summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/dockerfiles/Dockerfile.64-bit3
-rw-r--r--.github/dockerfiles/Dockerfile.cross-compile10
-rw-r--r--.github/dockerfiles/Dockerfile.debian-base53
-rw-r--r--.github/dockerfiles/Dockerfile.documentation3
-rwxr-xr-x.github/scripts/base-tag20
-rw-r--r--.github/workflows/main.yaml40
-rw-r--r--.github/workflows/update-base.yaml57
-rw-r--r--HOWTO/DEPRECATE.md79
-rw-r--r--OTP_VERSION2
-rw-r--r--bootstrap/bin/no_dot_erlang.bootbin6763 -> 6761 bytes
-rw-r--r--bootstrap/bin/start.bootbin6763 -> 6761 bytes
-rw-r--r--bootstrap/bin/start_clean.bootbin6763 -> 6761 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_asm.beambin10932 -> 10932 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_digraph.beambin3500 -> 3508 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_jump.beambin10124 -> 10128 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_peep.beambin3544 -> 3572 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa.beambin14344 -> 14868 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_bool.beambin22624 -> 22656 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_codegen.beambin38388 -> 38544 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_dead.beambin12296 -> 12300 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_opt.beambin48064 -> 48192 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_pp.beambin6108 -> 6112 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_pre_codegen.beambin46604 -> 46916 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_recv.beambin4212 -> 4228 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_share.beambin5440 -> 5444 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_type.beambin33640 -> 33988 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_validator.beambin47216 -> 49080 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/compiler.app4
-rw-r--r--bootstrap/lib/compiler/ebin/core_parse.beambin61092 -> 61100 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/erl_bifs.beambin2112 -> 2096 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/sys_core_alias.beambin5360 -> 5452 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/v3_core.beambin60244 -> 60276 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/v3_kernel.beambin41172 -> 41184 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/disk_log.beambin28936 -> 28940 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/erl_epmd.beambin7344 -> 7368 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/file.beambin13520 -> 13564 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/gen_tcp_socket.beambin25524 -> 26212 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/global.beambin28052 -> 28056 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/hipe_unified_loader.beambin11912 -> 11912 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_db.beambin25864 -> 26096 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_res.beambin12776 -> 12920 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_tcp_dist.beambin7836 -> 7956 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/kernel.app4
-rw-r--r--bootstrap/lib/kernel/ebin/kernel_refc.beambin2220 -> 2224 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_formatter.beambin8864 -> 8868 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_olp.beambin7964 -> 7968 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_proxy.beambin2796 -> 2804 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_simple_h.beambin4268 -> 4272 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_std_h.beambin9568 -> 9572 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/net_kernel.beambin27568 -> 27656 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/pg.beambin7940 -> 7960 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/raw_file_io_deflate.beambin2584 -> 2584 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/socket.beambin12720 -> 12784 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/user_drv.beambin10916 -> 10996 bytes
-rw-r--r--bootstrap/lib/kernel/include/logger.hrl2
-rw-r--r--bootstrap/lib/stdlib/ebin/digraph.beambin7420 -> 7612 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/epp.beambin27612 -> 27668 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_eval.beambin34496 -> 34504 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_parse.beambin93660 -> 93664 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/error_logger_file_h.beambin3916 -> 3916 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/error_logger_tty_h.beambin4756 -> 4760 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/filelib.beambin10912 -> 11252 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/gen_server.beambin17932 -> 18060 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/gen_statem.beambin24248 -> 24252 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/io_lib_format.beambin14628 -> 14648 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/lists.beambin28700 -> 28736 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/maps.beambin3188 -> 3192 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/otp_internal.beambin8900 -> 9052 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/proplists.beambin4332 -> 4336 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/shell.beambin28272 -> 28276 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/shell_docs.beambin16428 -> 17148 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/stdlib.app4
-rw-r--r--bootstrap/lib/stdlib/ebin/supervisor.beambin23380 -> 23384 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/uri_string.beambin24596 -> 26548 bytes
-rw-r--r--erts/configure.in3
-rw-r--r--erts/doc/src/erl_cmd.xml7
-rw-r--r--erts/doc/src/erl_nif.xml58
-rw-r--r--erts/doc/src/escript_cmd.xml4
-rw-r--r--erts/doc/src/inet_cfg.xml13
-rw-r--r--erts/doc/src/notes.xml352
-rw-r--r--erts/emulator/beam/bif.c12
-rw-r--r--erts/emulator/beam/dist.c74
-rw-r--r--erts/emulator/beam/erl_alloc.types22
-rw-r--r--erts/emulator/beam/erl_map.c5
-rw-r--r--erts/emulator/beam/erl_monitor_link.c20
-rw-r--r--erts/emulator/beam/erl_monitor_link.h26
-rw-r--r--erts/emulator/beam/erl_nif.c9
-rw-r--r--erts/emulator/beam/erl_process.c50
-rw-r--r--erts/emulator/beam/erl_process.h1
-rw-r--r--erts/emulator/beam/erl_process_dict.c2
-rw-r--r--erts/emulator/beam/external.c8
-rw-r--r--erts/emulator/beam/external.h1
-rw-r--r--erts/emulator/beam/ops.tab3
-rw-r--r--erts/emulator/nifs/common/prim_file_nif.c8
-rw-r--r--erts/emulator/sys/common/erl_poll.c18
-rw-r--r--erts/emulator/sys/win32/erl_win_sys.h4
-rw-r--r--erts/emulator/test/bs_bincomp_SUITE.erl14
-rw-r--r--erts/emulator/test/distribution_SUITE.erl86
-rw-r--r--erts/emulator/test/driver_SUITE.erl79
-rw-r--r--erts/emulator/test/driver_SUITE_data/Makefile.src14
-rw-r--r--erts/emulator/test/driver_SUITE_data/lots_of_fds_used_wrapper.c61
-rw-r--r--erts/emulator/test/driver_SUITE_data/peek_non_existing_queue_drv.c4
-rw-r--r--erts/emulator/test/driver_SUITE_data/thr_msg_blast_drv.c4
-rw-r--r--erts/emulator/test/estone_SUITE_data/estone_cat.c8
-rw-r--r--erts/emulator/test/mtx_SUITE_data/mtx_SUITE.c1
-rw-r--r--erts/emulator/test/nif_SUITE.erl15
-rw-r--r--erts/emulator/test/nif_SUITE_data/nif_SUITE.c58
-rw-r--r--erts/emulator/test/num_bif_SUITE.erl45
-rw-r--r--erts/emulator/test/port_bif_SUITE_data/port_test.c3
-rw-r--r--erts/emulator/test/trace_SUITE_data/slow_drv.c6
-rwxr-xr-xerts/emulator/utils/make_driver_tab1
-rw-r--r--erts/emulator/valgrind/suppress.standard31
-rw-r--r--erts/etc/common/erlexec.c6
-rw-r--r--erts/etc/unix/etp-commands.in147
-rw-r--r--erts/etc/win32/erl.c5
-rw-r--r--erts/etc/win32/manifest.xml17
-rw-r--r--erts/etc/win32/nsis/erlang20.nsi14
-rw-r--r--erts/etc/win32/win_erlexec.c5
-rwxr-xr-xerts/etc/win32/wsl_tools/vc/ld.sh10
-rw-r--r--erts/lib_src/Makefile.in1
-rw-r--r--erts/vsn.mk2
-rw-r--r--lib/common_test/doc/src/Makefile5
-rw-r--r--lib/common_test/doc/src/common_test_app.xml557
-rw-r--r--lib/common_test/doc/src/ct.xml6
-rw-r--r--lib/common_test/doc/src/ct_hooks.xml70
-rw-r--r--lib/common_test/doc/src/ct_hooks_chapter.xml36
-rw-r--r--lib/common_test/doc/src/ct_property_test.xml2
-rw-r--r--lib/common_test/doc/src/ct_suite.xml619
-rw-r--r--lib/common_test/doc/src/dependencies_chapter.xml10
-rw-r--r--lib/common_test/doc/src/notes.xml15
-rw-r--r--lib/common_test/doc/src/ref_man.xml1
-rw-r--r--lib/common_test/doc/src/specs.xml1
-rw-r--r--lib/common_test/doc/src/test_structure_chapter.xml4
-rw-r--r--lib/common_test/doc/src/write_test_chapter.xml22
-rw-r--r--lib/common_test/src/Makefile11
-rw-r--r--lib/common_test/src/common_test.app.src3
-rw-r--r--lib/common_test/src/ct_suite.erl130
-rw-r--r--lib/common_test/vsn.mk2
-rw-r--r--lib/compiler/doc/src/notes.xml60
-rw-r--r--lib/compiler/src/beam_ssa_opt.erl4
-rw-r--r--lib/compiler/src/beam_ssa_pre_codegen.erl16
-rw-r--r--lib/compiler/src/beam_ssa_type.erl25
-rw-r--r--lib/compiler/src/beam_validator.erl49
-rw-r--r--lib/compiler/src/erl_bifs.erl10
-rw-r--r--lib/compiler/src/v3_core.erl13
-rw-r--r--lib/compiler/test/beam_type_SUITE.erl41
-rw-r--r--lib/compiler/test/beam_validator_SUITE.erl22
-rw-r--r--lib/compiler/test/bs_bincomp_SUITE.erl30
-rw-r--r--lib/compiler/test/bs_construct_SUITE.erl12
-rw-r--r--lib/compiler/test/bs_match_SUITE.erl13
-rw-r--r--lib/compiler/test/fun_SUITE.erl7
-rw-r--r--lib/compiler/vsn.mk2
-rw-r--r--lib/crypto/c_src/Makefile.in10
-rw-r--r--lib/crypto/c_src/algorithms.c699
-rw-r--r--lib/crypto/c_src/cipher.c82
-rw-r--r--lib/crypto/c_src/crypto.c1
-rw-r--r--lib/crypto/c_src/crypto_callback.c24
-rw-r--r--lib/crypto/c_src/crypto_callback.h2
-rw-r--r--lib/crypto/c_src/hmac.c19
-rw-r--r--lib/crypto/c_src/hmac.h2
-rw-r--r--lib/crypto/c_src/mac.c4
-rw-r--r--lib/crypto/c_src/openssl_config.h18
-rw-r--r--lib/crypto/c_src/otp_test_engine.c2
-rw-r--r--lib/crypto/c_src/pkey.c29
-rw-r--r--lib/crypto/c_src/srp.c5
-rw-r--r--lib/crypto/configure.in17
-rw-r--r--lib/crypto/doc/src/Makefile2
-rw-r--r--lib/crypto/doc/src/crypto.xml7
-rw-r--r--lib/crypto/doc/src/notes.xml76
-rw-r--r--lib/crypto/src/crypto.erl72
-rw-r--r--lib/crypto/test/crypto_SUITE.erl58
-rw-r--r--lib/crypto/vsn.mk2
-rw-r--r--lib/debugger/doc/src/int.xml2
-rw-r--r--lib/dialyzer/doc/src/dialyzer.xml4
-rw-r--r--lib/dialyzer/doc/src/notes.xml16
-rw-r--r--lib/dialyzer/vsn.mk2
-rw-r--r--lib/erl_docgen/doc/src/doc-build.xml8
-rw-r--r--lib/erl_docgen/doc/src/notes.xml24
-rw-r--r--lib/erl_docgen/priv/css/otp_doc.css37
-rw-r--r--lib/erl_docgen/priv/xsl/db_html.xsl123
-rw-r--r--lib/erl_docgen/vsn.mk2
-rw-r--r--lib/erl_interface/doc/src/notes.xml43
-rw-r--r--lib/erl_interface/src/misc/ei_printterm.c69
-rw-r--r--lib/erl_interface/test/all_SUITE_data/ei_runner.c2
-rw-r--r--lib/erl_interface/test/ei_connect_SUITE_data/einode.c6
-rw-r--r--lib/erl_interface/test/ei_format_SUITE_data/ei_format_test.c1
-rw-r--r--lib/erl_interface/test/ei_print_SUITE.erl68
-rw-r--r--lib/erl_interface/test/ei_print_SUITE_data/ei_print_test.c67
-rw-r--r--lib/erl_interface/test/ei_tmo_SUITE_data/ei_tmo_test.c1
-rw-r--r--lib/erl_interface/vsn.mk2
-rw-r--r--lib/inets/doc/src/notes.xml17
-rw-r--r--lib/inets/src/http_server/mod_alias.erl5
-rw-r--r--lib/inets/src/http_server/mod_dir.erl69
-rw-r--r--lib/inets/test/httpd_SUITE_data/cgi_echo.c2
-rw-r--r--lib/inets/vsn.mk2
-rw-r--r--lib/kernel/doc/src/code.xml6
-rw-r--r--lib/kernel/doc/src/eep48_chapter.xml3
-rw-r--r--lib/kernel/doc/src/inet_res.xml56
-rw-r--r--lib/kernel/doc/src/notes.xml71
-rw-r--r--lib/kernel/include/logger.hrl2
-rw-r--r--lib/kernel/src/erl_epmd.erl10
-rw-r--r--lib/kernel/src/inet_db.erl133
-rw-r--r--lib/kernel/src/inet_res.erl16
-rw-r--r--lib/kernel/src/inet_res.hrl46
-rw-r--r--lib/kernel/src/kernel.appup.src8
-rw-r--r--lib/kernel/src/logger_proxy.erl6
-rw-r--r--lib/kernel/src/os.erl6
-rw-r--r--lib/kernel/src/pg.erl4
-rw-r--r--lib/kernel/test/erl_distribution_SUITE.erl11
-rw-r--r--lib/kernel/test/esock_misc/esock_iow_client.erl (renamed from erts/emulator/test/esock_misc/esock_iow_client.erl)0
-rw-r--r--lib/kernel/test/esock_misc/esock_iow_lib.erl (renamed from erts/emulator/test/esock_misc/esock_iow_lib.erl)0
-rw-r--r--lib/kernel/test/esock_misc/esock_iow_server.erl (renamed from erts/emulator/test/esock_misc/esock_iow_server.erl)0
-rw-r--r--lib/kernel/test/esock_misc/socket_client.erl (renamed from erts/emulator/test/esock_misc/socket_client.erl)0
-rw-r--r--lib/kernel/test/esock_misc/socket_lib.erl (renamed from erts/emulator/test/esock_misc/socket_lib.erl)0
-rw-r--r--lib/kernel/test/esock_misc/socket_server.erl (renamed from erts/emulator/test/esock_misc/socket_server.erl)0
-rw-r--r--lib/kernel/test/esock_ttest/.gitignore (renamed from erts/emulator/test/esock_ttest/.gitignore)0
-rwxr-xr-xlib/kernel/test/esock_ttest/esock-ttest (renamed from erts/emulator/test/esock_ttest/esock-ttest)0
-rwxr-xr-xlib/kernel/test/esock_ttest/esock-ttest-client (renamed from erts/emulator/test/esock_ttest/esock-ttest-client)0
-rwxr-xr-xlib/kernel/test/esock_ttest/esock-ttest-server-gen (renamed from erts/emulator/test/esock_ttest/esock-ttest-server-gen)0
-rwxr-xr-xlib/kernel/test/esock_ttest/esock-ttest-server-sock (renamed from erts/emulator/test/esock_ttest/esock-ttest-server-sock)0
-rw-r--r--lib/kernel/test/inet_SUITE.erl2
-rw-r--r--lib/kernel/test/inet_res_SUITE.erl361
-rw-r--r--lib/kernel/test/kernel_test_lib.erl4
-rw-r--r--lib/kernel/test/logger_SUITE.erl8
-rw-r--r--lib/kernel/test/os_SUITE_data/my_fds.c4
-rw-r--r--lib/kernel/test/pg_SUITE.erl27
-rw-r--r--lib/kernel/test/prim_file_SUITE.erl59
-rw-r--r--lib/kernel/vsn.mk2
-rw-r--r--lib/megaco/configure.in7
-rw-r--r--lib/megaco/doc/src/notes.xml50
-rw-r--r--lib/megaco/src/text/megaco_text_gen_v3.hrl14
-rw-r--r--lib/megaco/src/text/megaco_text_parser_v3.hrl32
-rw-r--r--lib/megaco/src/text/megaco_text_parser_v3.yrl21
-rw-r--r--lib/megaco/test/megaco_codec_v3_SUITE.erl135
-rw-r--r--lib/megaco/test/megaco_test_lib.erl2
-rw-r--r--lib/megaco/test/megaco_test_msg_v3_lib.erl33
-rw-r--r--lib/megaco/test/megaco_trans_SUITE.erl66
-rw-r--r--lib/megaco/vsn.mk2
-rw-r--r--lib/mnesia/doc/src/notes.xml34
-rw-r--r--lib/mnesia/src/mnesia_dumper.erl5
-rw-r--r--lib/mnesia/vsn.mk2
-rw-r--r--lib/odbc/configure.in7
-rw-r--r--lib/odbc/doc/src/notes.xml18
-rw-r--r--lib/odbc/test/README2
-rw-r--r--lib/odbc/test/postgres.erl6
-rw-r--r--lib/odbc/vsn.mk2
-rw-r--r--lib/os_mon/doc/src/notes.xml24
-rw-r--r--lib/public_key/doc/src/notes.xml15
-rw-r--r--lib/public_key/src/public_key.erl2
-rw-r--r--lib/public_key/vsn.mk2
-rw-r--r--lib/snmp/configure.in7
-rw-r--r--lib/snmp/doc/src/notes.xml147
-rw-r--r--lib/snmp/doc/src/snmp_agent_config_files.xml84
-rw-r--r--lib/snmp/doc/src/snmp_app.xml87
-rw-r--r--lib/snmp/doc/src/snmp_config.xml67
-rw-r--r--lib/snmp/src/agent/snmp_framework_mib.erl250
-rw-r--r--lib/snmp/src/agent/snmpa_agent.erl121
-rw-r--r--lib/snmp/src/agent/snmpa_mib.erl216
-rw-r--r--lib/snmp/src/agent/snmpa_mpd.erl23
-rw-r--r--lib/snmp/src/agent/snmpa_net_if.erl866
-rw-r--r--lib/snmp/src/agent/snmpa_trap.erl75
-rw-r--r--lib/snmp/src/agent/snmpa_usm.erl5
-rw-r--r--lib/snmp/src/app/snmp.erl10
-rw-r--r--lib/snmp/src/manager/snmpm_config.erl4
-rw-r--r--lib/snmp/src/manager/snmpm_net_if.erl48
-rw-r--r--lib/snmp/src/manager/snmpm_server.erl54
-rw-r--r--lib/snmp/src/misc/snmp_conf.erl172
-rw-r--r--lib/snmp/src/misc/snmp_config.erl295
-rw-r--r--lib/snmp/src/misc/snmp_verbosity.erl4
-rw-r--r--lib/snmp/test/modules.mk1
-rw-r--r--lib/snmp/test/snmp_agent_SUITE.erl854
-rw-r--r--lib/snmp/test/snmp_agent_mibs_SUITE.erl299
-rw-r--r--lib/snmp/test/snmp_agent_test_lib.erl86
-rw-r--r--lib/snmp/test/snmp_conf_SUITE.erl179
-rw-r--r--lib/snmp/test/snmp_manager_SUITE.erl20
-rw-r--r--lib/snmp/test/snmp_manager_config_SUITE.erl82
-rw-r--r--lib/snmp/test/snmp_otp16649_user.erl93
-rw-r--r--lib/snmp/test/snmp_test_lib.erl84
-rw-r--r--lib/snmp/test/snmp_test_lib.hrl2
-rw-r--r--lib/snmp/test/snmp_test_mgr.erl83
-rw-r--r--lib/snmp/test/snmp_test_mgr_misc.erl195
-rw-r--r--lib/snmp/vsn.mk2
-rw-r--r--lib/ssh/doc/src/hardening.xml37
-rw-r--r--lib/ssh/doc/src/notes.xml102
-rw-r--r--lib/ssh/doc/src/ssh.xml30
-rw-r--r--lib/ssh/src/ssh.erl6
-rw-r--r--lib/ssh/src/ssh.hrl8
-rw-r--r--lib/ssh/src/ssh_auth.erl53
-rw-r--r--lib/ssh/src/ssh_cli.erl322
-rw-r--r--lib/ssh/src/ssh_connect.hrl1
-rw-r--r--lib/ssh/src/ssh_connection.erl162
-rw-r--r--lib/ssh/src/ssh_connection_handler.erl22
-rw-r--r--lib/ssh/src/ssh_dbg.erl2
-rw-r--r--lib/ssh/src/ssh_message.erl26
-rw-r--r--lib/ssh/src/ssh_options.erl6
-rw-r--r--lib/ssh/src/ssh_shell.erl80
-rw-r--r--lib/ssh/test/ssh_basic_SUITE.erl161
-rw-r--r--lib/ssh/test/ssh_connection_SUITE.erl110
-rw-r--r--lib/ssh/test/ssh_options_SUITE.erl15
-rw-r--r--lib/ssh/test/ssh_sup_SUITE.erl1
-rw-r--r--lib/ssh/test/ssh_test_lib.erl10
-rw-r--r--lib/ssh/vsn.mk2
-rw-r--r--lib/ssl/doc/src/notes.xml237
-rw-r--r--lib/ssl/doc/src/ssl.xml44
-rw-r--r--lib/ssl/doc/src/ssl_protocol.xml48
-rw-r--r--lib/ssl/doc/src/standards_compliance.xml48
-rw-r--r--lib/ssl/doc/src/using_ssl.xml176
-rw-r--r--lib/ssl/src/Makefile8
-rw-r--r--lib/ssl/src/dtls_connection.erl1068
-rw-r--r--lib/ssl/src/dtls_connection_sup.erl4
-rw-r--r--lib/ssl/src/dtls_gen_connection.erl685
-rw-r--r--lib/ssl/src/dtls_handshake.erl17
-rw-r--r--lib/ssl/src/dtls_listener_sup.erl32
-rw-r--r--lib/ssl/src/dtls_packet_demux.erl58
-rw-r--r--lib/ssl/src/dtls_server_session_cache_sup.erl4
-rw-r--r--lib/ssl/src/dtls_socket.erl122
-rw-r--r--lib/ssl/src/inet6_tls_dist.erl7
-rw-r--r--lib/ssl/src/inet_tls_dist.erl29
-rw-r--r--lib/ssl/src/ssl.app.src10
-rw-r--r--lib/ssl/src/ssl.erl123
-rw-r--r--lib/ssl/src/ssl_certificate.erl258
-rw-r--r--lib/ssl/src/ssl_cipher.erl6
-rw-r--r--lib/ssl/src/ssl_cipher.hrl12
-rw-r--r--lib/ssl/src/ssl_cipher_format.erl350
-rw-r--r--lib/ssl/src/ssl_config.erl75
-rw-r--r--lib/ssl/src/ssl_connection.erl3182
-rw-r--r--lib/ssl/src/ssl_connection.hrl3
-rw-r--r--lib/ssl/src/ssl_dist_connection_sup.erl40
-rw-r--r--lib/ssl/src/ssl_dist_sup.erl8
-rw-r--r--lib/ssl/src/ssl_gen_statem.erl2011
-rw-r--r--lib/ssl/src/ssl_handshake.erl183
-rw-r--r--lib/ssl/src/ssl_handshake.hrl2
-rw-r--r--lib/ssl/src/ssl_internal.hrl3
-rw-r--r--lib/ssl/src/ssl_listen_tracker_sup.erl2
-rw-r--r--lib/ssl/src/ssl_manager.erl4
-rw-r--r--lib/ssl/src/ssl_record.erl3
-rw-r--r--lib/ssl/src/ssl_server_session_cache.erl15
-rw-r--r--lib/ssl/src/ssl_server_session_cache_db.erl2
-rw-r--r--lib/ssl/src/ssl_server_session_cache_sup.erl61
-rw-r--r--lib/ssl/src/ssl_session.erl39
-rw-r--r--lib/ssl/src/ssl_session_cache.erl4
-rw-r--r--lib/ssl/src/ssl_upgrade_server_session_cache_sup.erl90
-rw-r--r--lib/ssl/src/tls_connection.erl1437
-rw-r--r--lib/ssl/src/tls_connection_1_3.erl446
-rw-r--r--lib/ssl/src/tls_connection_sup.erl8
-rw-r--r--lib/ssl/src/tls_dist_server_sup.erl89
-rw-r--r--lib/ssl/src/tls_dist_sup.erl75
-rw-r--r--lib/ssl/src/tls_dtls_connection.erl1687
-rw-r--r--lib/ssl/src/tls_gen_connection.erl767
-rw-r--r--lib/ssl/src/tls_handshake.erl9
-rw-r--r--lib/ssl/src/tls_handshake_1_3.erl377
-rw-r--r--lib/ssl/src/tls_sender.erl8
-rw-r--r--lib/ssl/src/tls_server_session_ticket_sup.erl26
-rw-r--r--lib/ssl/src/tls_server_sup.erl15
-rw-r--r--lib/ssl/src/tls_socket.erl64
-rw-r--r--lib/ssl/src/tls_sup.erl6
-rw-r--r--lib/ssl/src/tls_v1.erl76
-rw-r--r--lib/ssl/test/dtls_api_SUITE.erl136
-rw-r--r--lib/ssl/test/openssl_cipher_suite_SUITE.erl79
-rw-r--r--lib/ssl/test/openssl_client_cert_SUITE.erl3
-rw-r--r--lib/ssl/test/openssl_server_cert_SUITE.erl4
-rw-r--r--lib/ssl/test/openssl_session_SUITE.erl69
-rw-r--r--lib/ssl/test/property_test/ssl_eqc_chain.erl411
-rw-r--r--lib/ssl/test/property_test/ssl_eqc_cipher_format.erl181
-rw-r--r--lib/ssl/test/ssl_ECC.erl2
-rw-r--r--lib/ssl/test/ssl_ECC_SUITE.erl6
-rw-r--r--lib/ssl/test/ssl_alert_SUITE.erl2
-rw-r--r--lib/ssl/test/ssl_alpn_SUITE.erl4
-rw-r--r--lib/ssl/test/ssl_api_SUITE.erl167
-rw-r--r--lib/ssl/test/ssl_app_env_SUITE.erl2
-rw-r--r--lib/ssl/test/ssl_basic_SUITE.erl330
-rw-r--r--lib/ssl/test/ssl_bench_SUITE.erl2
-rw-r--r--lib/ssl/test/ssl_bench_test_lib.erl2
-rw-r--r--lib/ssl/test/ssl_cert_SUITE.erl10
-rw-r--r--lib/ssl/test/ssl_cert_tests.erl16
-rw-r--r--lib/ssl/test/ssl_cipher_SUITE.erl2
-rw-r--r--lib/ssl/test/ssl_cipher_suite_SUITE.erl48
-rw-r--r--lib/ssl/test/ssl_crl_SUITE.erl2
-rw-r--r--lib/ssl/test/ssl_dist_SUITE.erl2
-rw-r--r--lib/ssl/test/ssl_dist_bench_SUITE.erl2
-rw-r--r--lib/ssl/test/ssl_dist_test_lib.erl2
-rw-r--r--lib/ssl/test/ssl_engine_SUITE.erl2
-rw-r--r--lib/ssl/test/ssl_eqc_SUITE.erl51
-rw-r--r--lib/ssl/test/ssl_handshake_SUITE.erl23
-rw-r--r--lib/ssl/test/ssl_key_update_SUITE.erl2
-rw-r--r--lib/ssl/test/ssl_mfl_SUITE.erl3
-rw-r--r--lib/ssl/test/ssl_npn_SUITE.erl2
-rw-r--r--lib/ssl/test/ssl_npn_hello_SUITE.erl2
-rw-r--r--lib/ssl/test/ssl_packet_SUITE.erl2
-rw-r--r--lib/ssl/test/ssl_payload_SUITE.erl2
-rw-r--r--lib/ssl/test/ssl_pem_cache_SUITE.erl2
-rw-r--r--lib/ssl/test/ssl_renegotiate_SUITE.erl2
-rw-r--r--lib/ssl/test/ssl_rfc_5869_SUITE.erl2
-rw-r--r--lib/ssl/test/ssl_session_SUITE.erl62
-rw-r--r--lib/ssl/test/ssl_session_cache_SUITE.erl2
-rw-r--r--lib/ssl/test/ssl_session_ticket_SUITE.erl2
-rw-r--r--lib/ssl/test/ssl_sni_SUITE.erl4
-rw-r--r--lib/ssl/test/ssl_socket_SUITE.erl2
-rw-r--r--lib/ssl/test/ssl_test_lib.erl117
-rw-r--r--lib/ssl/test/ssl_upgrade_SUITE.erl2
-rw-r--r--lib/ssl/test/tls_1_3_version_SUITE.erl94
-rw-r--r--lib/ssl/test/tls_api_SUITE.erl115
-rw-r--r--lib/ssl/vsn.mk2
-rw-r--r--lib/stdlib/doc/src/Makefile2
-rw-r--r--lib/stdlib/doc/src/ets.xml18
-rw-r--r--lib/stdlib/doc/src/notes.xml68
-rw-r--r--lib/stdlib/doc/src/part.xml1
-rw-r--r--lib/stdlib/doc/src/shell_docs.xml81
-rw-r--r--lib/stdlib/doc/src/supervisor.xml2
-rw-r--r--lib/stdlib/doc/src/uri_string.xml49
-rw-r--r--lib/stdlib/doc/src/uri_string_usage.xml370
-rw-r--r--lib/stdlib/src/gen_server.erl9
-rw-r--r--lib/stdlib/src/otp_internal.erl24
-rw-r--r--lib/stdlib/src/shell_docs.erl334
-rw-r--r--lib/stdlib/src/stdlib.appup.src2
-rw-r--r--lib/stdlib/src/uri_string.erl129
-rw-r--r--lib/stdlib/test/property_test/uri_string_recompose.erl9
-rw-r--r--lib/stdlib/test/shell_docs_SUITE.erl67
-rw-r--r--lib/stdlib/test/uri_string_SUITE.erl172
-rw-r--r--lib/stdlib/vsn.mk2
-rw-r--r--lib/syntax_tools/doc/src/notes.xml17
-rw-r--r--lib/syntax_tools/src/erl_tidy.erl5
-rw-r--r--lib/syntax_tools/src/igor.erl2
-rw-r--r--lib/syntax_tools/vsn.mk2
-rw-r--r--lib/tools/doc/src/notes.xml32
-rw-r--r--lib/tools/doc/src/xref.xml4
-rw-r--r--lib/tools/src/xref_reader.erl24
-rw-r--r--lib/tools/src/xref_utils.erl2
-rw-r--r--lib/tools/test/emacs_SUITE.erl2
-rw-r--r--lib/tools/test/xref_SUITE.erl83
-rw-r--r--lib/tools/test/xref_SUITE_data/lib_test/bi.erl3
-rw-r--r--lib/tools/test/xref_SUITE_data/lib_test/no_bi.erl6
-rw-r--r--lib/tools/vsn.mk2
-rw-r--r--lib/wx/api_gen/wx_extra/wxTaskBarIcon.c_src46
-rw-r--r--lib/wx/api_gen/wx_extra/wxTaskBarIcon.erl45
-rw-r--r--lib/wx/api_gen/wx_gen.erl4
-rw-r--r--lib/wx/api_gen/wx_gen_erl.erl57
-rw-r--r--lib/wx/api_gen/wxapi.conf2
-rw-r--r--lib/wx/c_src/gen/wxe_derived_dest.h9
-rw-r--r--lib/wx/c_src/gen/wxe_funcs.cpp10
-rw-r--r--lib/wx/c_src/wxe_callback_impl.cpp20
-rw-r--r--lib/wx/c_src/wxe_main.cpp15
-rw-r--r--lib/wx/c_src/wxe_ps_init.c2
-rw-r--r--lib/wx/doc/src/notes.xml15
-rw-r--r--lib/wx/examples/simple/menu.erl3
-rw-r--r--lib/wx/src/gen/wxTaskBarIcon.erl24
-rw-r--r--lib/wx/vsn.mk2
-rw-r--r--lib/xmerl/doc/src/notes.xml23
-rw-r--r--lib/xmerl/src/xmerl_sax_old_dom.erl157
-rw-r--r--lib/xmerl/src/xmerl_sax_parser_base.erlsrc7
-rw-r--r--lib/xmerl/vsn.mk2
-rw-r--r--make/app_targets.mk2
-rw-r--r--make/configure.in48
-rw-r--r--make/otp_patch_solve_forward_merge_version2
-rw-r--r--make/otp_release_targets.mk7
-rw-r--r--make/otp_version_tickets4
-rw-r--r--make/otp_version_tickets_in_merge4
-rw-r--r--otp_versions.table10
-rwxr-xr-xscripts/otp_html_check4
-rw-r--r--system/doc/general_info/DEPRECATIONS9
-rw-r--r--system/doc/reference_manual/errors.xml2
461 files changed, 21015 insertions, 9303 deletions
diff --git a/.github/dockerfiles/Dockerfile.64-bit b/.github/dockerfiles/Dockerfile.64-bit
index 0ee206d55f..c651cbc618 100644
--- a/.github/dockerfiles/Dockerfile.64-bit
+++ b/.github/dockerfiles/Dockerfile.64-bit
@@ -15,8 +15,7 @@ RUN cd /buildroot && tar -xzf ./otp.tar.gz
WORKDIR /buildroot/otp/
-RUN ./configure --prefix=/otp && make && make install && \
- make install-docs DOC_TARGETS=chunks
+RUN ./configure --prefix=/otp && make && make install
RUN TESTSUITE_ROOT=/tests ./otp_build tests
diff --git a/.github/dockerfiles/Dockerfile.cross-compile b/.github/dockerfiles/Dockerfile.cross-compile
index 868f8beed1..98f7f0e576 100644
--- a/.github/dockerfiles/Dockerfile.cross-compile
+++ b/.github/dockerfiles/Dockerfile.cross-compile
@@ -3,9 +3,6 @@
##
FROM docker.pkg.github.com/erlang/otp/i386-debian-base as build
-ARG HOST_ARCH=amd64
-ARG HOST_TRIP=x86_64-linux-gnu
-
ARG MAKEFLAGS=-j4
ENV MAKEFLAGS=$MAKEFLAGS \
ERLC_USE_SERVER=yes \
@@ -21,7 +18,8 @@ WORKDIR /buildroot/otp/
RUN ./configure && make && make install
## Build pre-build tar ball
-RUN scripts/build-otp-tar -o /buildroot/otp_clean_src.tar.gz /buildroot/otp_src.tar.gz -b /buildroot/otp/ /buildroot/otp.tar.gz
+RUN scripts/build-otp-tar -o /buildroot/otp_clean_src.tar.gz /buildroot/otp_src.tar.gz \
+ -b /buildroot/otp/ /buildroot/otp.tar.gz
## Prepare for a new build using pre-built tar ball
RUN cd .. && rm -rf otp && tar -xzf ./otp_src.tar.gz
@@ -43,7 +41,9 @@ RUN ./configure --prefix=/otp/ --host=$HOST --build=`erts/autoconf/config.guess`
## Build the cross tests
RUN ./otp_build tests
RUN cd release/tests/test_server && \
- erl -sname test@docker -noshell -eval "ts:install([{cross,\"yes\"},{crossflags,[{\"host\",\"$HOST\"}]},{crossroot,\"/$ERL_TOP\"}])." -s ts compile_testcases -s init stop
+ erl -sname test@docker -noshell \
+ -eval "ts:install([{cross,\"yes\"},{crossflags,[{\"host\",\"$HOST\"}]},{crossroot,\"/$ERL_TOP\"}])." \
+ -s ts compile_testcases -s init stop
FROM debian as install
diff --git a/.github/dockerfiles/Dockerfile.debian-base b/.github/dockerfiles/Dockerfile.debian-base
index 1c26677959..416edd97c9 100644
--- a/.github/dockerfiles/Dockerfile.debian-base
+++ b/.github/dockerfiles/Dockerfile.debian-base
@@ -6,29 +6,38 @@ FROM $BASE
## Need to have a second arg here as the first does not expose the $BASE in the script below
ARG BASE=debian
-ARG HOST_ARCH=amd64
ARG HOST_TRIP=x86_64-linux-gnu
+ENV HOST_TRIP=$HOST_TRIP
-ENV INSTALL_LIBS="zlib1g-dev libncurses5-dev libssh-dev unixodbc-dev libgmp3-dev libwxbase3.0-dev libwxgtk3.0-dev libsctp-dev lksctp-tools"
+ENV INSTALL_LIBS="zlib1g-dev libncurses5-dev libssh-dev unixodbc-dev libgmp3-dev libwxbase3.0-dev libwxgtk3.0-dev libwxgtk-webview3.0-gtk3-dev libsctp-dev lksctp-tools"
## See https://wiki.debian.org/Multiarch/HOWTO for details on how to install things
-RUN if [ "$BASE" = "i386/debian" ]; then BUILD_ARCH=`dpkg --print-architecture` && \
- dpkg --add-architecture $HOST_ARCH && \
- sed -i "s:deb http:deb [arch=$BUILD_ARCH,$HOST_ARCH] http:g" /etc/apt/sources.list; \
- fi
-
-RUN apt-get update && \
- apt-get -y upgrade && \
- apt-get install -y build-essential m4 autoconf fop xsltproc \
- default-jdk libxml2-utils $INSTALL_LIBS
-
-RUN if [ "$BASE" = "i386/debian" ]; then apt-get install -y \
- crossbuild-essential-$HOST_ARCH \
- $(for LIB in $INSTALL_LIBS; do echo "$LIB:$HOST_ARCH"; done) && \
- for dir in `find / -type d -name $HOST_TRIP`; do \
- echo -n "$dir: /buildroot/sysroot"; \
- echo `dirname $dir`; \
- mkdir -p /buildroot/sysroot$dir; \
- cp -r `dirname $dir`/* `dirname /buildroot/sysroot$dir`; \
- cp -r $dir/* `dirname /buildroot/sysroot$dir`; \
- done; fi
+##
+## 1. Install build-essential to get access to dpkg-architecture
+## 2. Use dpkg-architecture to figure out what we are runnon on
+## 3. If the HOST_TRIP does not equal BUILD_TRIP we should cross compile
+RUN apt-get update && apt-get -y upgrade && apt-get install -y build-essential && \
+ BUILD_TRIP=`dpkg-architecture -t${HOST_TRIP} -qDEB_BUILD_MULTIARCH` && \
+ BUILD_ARCH=`dpkg-architecture -t${HOST_TRIP} -qDEB_BUILD_ARCH` && \
+ if [ "$HOST_TRIP" != "$BUILD_TRIP" ]; then \
+ HOST_ARCH=`dpkg-architecture -t${HOST_TRIP} -qDEB_HOST_ARCH` && \
+ dpkg --add-architecture $HOST_ARCH && \
+ sed -i "s:deb http:deb [arch=$BUILD_ARCH,$HOST_ARCH] http:g" /etc/apt/sources.list; \
+ fi && \
+ apt-get update && \
+ apt-get install -y build-essential m4 autoconf fop xsltproc default-jdk libxml2-utils \
+ $INSTALL_LIBS && \
+ if [ "$HOST_TRIP" != "$BUILD_TRIP" ]; then \
+ apt-get install -y \
+ crossbuild-essential-$HOST_ARCH \
+ $(for LIB in $INSTALL_LIBS; do echo "$LIB:$HOST_ARCH"; done) && \
+ for dir in `find / -type d -name $HOST_TRIP`; do \
+ echo -n "$dir: /buildroot/sysroot"; \
+ echo `dirname $dir`; \
+ mkdir -p /buildroot/sysroot$dir; \
+ cp -r `dirname $dir`/* `dirname /buildroot/sysroot$dir`; \
+ cp -r $dir/* `dirname /buildroot/sysroot$dir`; \
+ done; \
+ fi && \
+ update-alternatives --set wx-config /usr/lib/${BUILD_TRIP}/wx/config/gtk3-unicode-3.0 && \
+ rm -rf /var/lib/apt/lists/*
diff --git a/.github/dockerfiles/Dockerfile.documentation b/.github/dockerfiles/Dockerfile.documentation
index 65d3910430..b0c2eb8015 100644
--- a/.github/dockerfiles/Dockerfile.documentation
+++ b/.github/dockerfiles/Dockerfile.documentation
@@ -12,8 +12,7 @@ RUN cd /buildroot && tar -xzf ./otp.tar.gz
WORKDIR /buildroot/otp/
-## We don't build pdf in order to save some time
-ENV RELEASE_ROOT=/otp DOC_TARGETS="html man chunks"
+ENV RELEASE_ROOT=/otp
RUN ./configure --prefix=/otp && make && make release
diff --git a/.github/scripts/base-tag b/.github/scripts/base-tag
new file mode 100755
index 0000000000..6683793762
--- /dev/null
+++ b/.github/scripts/base-tag
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+set -x
+
+case "$1" in
+ *i386-debian-base)
+ BASE="i386/debian"
+ BASE_TYPE=debian-base
+ ;;
+ *debian-base)
+ BASE="debian"
+ BASE_TYPE=debian-base
+ ;;
+ *ubuntu-base)
+ BASE="ubuntu"
+ BASE_TYPE=ubuntu-base
+ ;;
+esac
+echo "::set-output name=BASE::${BASE}"
+echo "::set-output name=BASE_TYPE::${BASE_TYPE}"
diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml
index 1eddf8f43c..988bf3b07e 100644
--- a/.github/workflows/main.yaml
+++ b/.github/workflows/main.yaml
@@ -10,6 +10,12 @@
## Also once the windows runner supports WSL we should implement
## support for building Erlang/OTP here.
##
+## When ghcr.io support using the GITHUB_TOKEN we should migrate
+## over to use it instead as that should allow us to use the
+## built-in caching mechanisms of docker/build-push-action@v2.
+## However as things are now we use docker directly to make things
+## work.
+##
name: Build and check Erlang/OTP
@@ -62,20 +68,35 @@ jobs:
uses: actions/download-artifact@v2
with:
name: otp_git_archive
- ## We need to login to the package registry in order to pull
- ## the base debian image.
- name: Docker login
- run: docker login https://docker.pkg.github.com -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
+ uses: docker/login-action@v1
+ with:
+ registry: docker.pkg.github.com
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+ - name: Calculate BASE image
+ id: base
+ run: |
+ BASE_TAG=$(grep "^FROM" .github/dockerfiles/Dockerfile.${{ matrix.type }} | head -1 | awk '{print $2}')
+ echo "::set-output name=BASE_TAG::${BASE_TAG}"
+ .github/scripts/base-tag "${BASE_TAG}"
+ - name: Pull BASE image
+ run: docker pull ${{ steps.base.outputs.BASE_TAG }}
+ - name: Build BASE image
+ run: |
+ docker build --pull --tag ${{ steps.base.outputs.BASE_TAG }} \
+ --cache-from ${{ steps.base.outputs.BASE_TAG }} \
+ --file .github/dockerfiles/Dockerfile.${{ steps.base.outputs.BASE_TYPE }} \
+ --build-arg BASE=${{ steps.base.outputs.BASE }} .
- name: Build ${{ matrix.type }} image
run: |
- docker build -t otp --build-arg ARCHIVE=otp_src.tar.gz \
- -f .github/dockerfiles/Dockerfile.${{ matrix.type }} .
+ docker build --tag otp --file .github/dockerfiles/Dockerfile.${{ matrix.type }} \
+ --build-arg ARCHIVE=otp_src.tar.gz .
## Smoke build tests
- if: matrix.type == '32-bit' || matrix.type == '64-bit' || matrix.type == 'cross-compile'
name: Run smoke test
- run: |
- docker run -v $PWD/scripts:/scripts otp "cd /tests && /scripts/run-smoke-tests"
+ run: docker run -v $PWD/scripts:/scripts otp "cd /tests && /scripts/run-smoke-tests"
## Documentation checks
- if: matrix.type == 'documentation'
@@ -162,7 +183,7 @@ jobs:
name: otp_doc_man
## We add the correct version name into the file names
- ## and create the MD5 file for all assets
+ ## and create the hash files for all assets
- name: Create pre-build and doc archives
run: |
mkdir artifacts
@@ -177,12 +198,13 @@ jobs:
run: |
scripts/bundle-otp ${{ steps.tag.outputs.tag }}
- ## Create md5sum
+ ## Create hash files
- name: Create pre-build and doc archives
run: |
shopt -s nullglob
cd artifacts
md5sum {*.tar.gz,*.txt} > MD5.txt
+ sha256sum {*.tar.gz,*.txt} > SHA256.txt
- name: Upload pre-built and doc tar archives
uses: softprops/action-gh-release@v1
diff --git a/.github/workflows/update-base.yaml b/.github/workflows/update-base.yaml
index ed250262e7..6cf2eafac2 100644
--- a/.github/workflows/update-base.yaml
+++ b/.github/workflows/update-base.yaml
@@ -9,48 +9,33 @@ on:
## Build base images to be used by other github workflows
jobs:
- build-debian-64bit:
+ build:
+ name: Update base Erlang/OTP build images
if: github.repository == 'erlang/otp'
runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
- - name: Build and push 64-bit base image
- uses: docker/build-push-action@v1
- with:
- username: ${{ github.actor }}
- password: ${{ secrets.GITHUB_TOKEN }}
- registry: docker.pkg.github.com
- dockerfile: .github/dockerfiles/Dockerfile.debian-base
- repository: erlang/otp/debian-base
- tags: latest
- build-debian-32bit:
- if: github.repository == 'erlang/otp'
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
- - name: Build and push 32-bit base image
- uses: docker/build-push-action@v1
- with:
- username: ${{ github.actor }}
- password: ${{ secrets.GITHUB_TOKEN }}
- registry: docker.pkg.github.com
- dockerfile: .github/dockerfiles/Dockerfile.debian-base
- build_args: "BASE=i386/debian"
- repository: erlang/otp/i386-debian-base
- tags: latest
+ strategy:
+ matrix:
+ type: [debian-base,ubuntu-base,i386-debian-base]
- build-ubuntu-64bit:
- if: github.repository == 'erlang/otp'
- runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- - name: Build and push 64-bit base image
- uses: docker/build-push-action@v1
+ - name: Docker login
+ uses: docker/login-action@v1
with:
+ registry: docker.pkg.github.com
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- registry: docker.pkg.github.com
- dockerfile: .github/dockerfiles/Dockerfile.ubuntu-base
- repository: erlang/otp/ubuntu-base
- tags: latest
+ - name: Calculate BASE image
+ id: base
+ run: |
+ echo "::set-output name=BASE_TAG::docker.pkg.github.com/erlang/otp/${{ matrix.type }}"
+ .github/scripts/base-tag "${{ matrix.type }}"
+ - name: Build base image
+ run: |
+ docker build --pull --tag ${{ steps.base.outputs.BASE_TAG }} \
+ --cache-from ${{ steps.base.outputs.BASE_TAG }} \
+ --file .github/dockerfiles/Dockerfile.${{ steps.base.outputs.BASE_TYPE }} \
+ --build-arg BASE=${{ steps.base.outputs.BASE }} .
+ - name: Push base image
+ run: docker push ${{ steps.base.outputs.BASE_TAG }}
diff --git a/HOWTO/DEPRECATE.md b/HOWTO/DEPRECATE.md
new file mode 100644
index 0000000000..a33b555e89
--- /dev/null
+++ b/HOWTO/DEPRECATE.md
@@ -0,0 +1,79 @@
+# Deprecate
+
+This HOWTO shows how to deprecate functionality from Erlang/OTP.
+
+When adding a new *deprecation* or *removal* warning you need to add an attribute to
+the module in question and update [$ERL_TOP/system/doc/general_info/DEPRECATIONS][1]
+with information about when the deprecation was added.
+
+After changing or adding an attribute, and updating the [`DEPRECATIONS`][1],
+you need to update the internal state by running:
+
+ $ ./otp_build update_deprecations
+
+This will update the documentation and the central list of deprecated and removed
+interfaces ([`otp_internal.erl`][2]).
+
+## Attribute format
+
+To mark a function/type as deprecated or removed, point out its name and arity
+together with a suggestion on what the user should do instead:
+
+ -deprecated([{now,0,
+ "see the \"Time and Time Correction in Erlang\" "
+ "chapter of the ERTS User's Guide for more information"}]).
+
+ -deprecated([{cmac, 3, "use crypto:mac/4 instead"},
+ {cmac, 4, "use crypto:macN/5 instead"}]).
+
+ -removed([{md5_mac,2,"use crypto:hmac/3 instead"},
+ {md5_mac_96,2,"use crypto:hmac/4 instead"}]).
+
+ -deprecated_type([{gadget,1,"use widget/1 instead"}]).
+
+ -removed_type([{column,0,"use erl_anno:column() instead"},
+ {line,0,"use erl_anno:line() instead"},
+ {location,0,"use erl_anno:location() instead"}]).
+
+Wildcards can be used to match all names and/or arities:
+
+ -removed([{rsa_sign,'_',"use crypto:sign/4 instead"},
+ {rsa_verify,'_',"use crypto:verify/5 instead"}]).
+
+ -deprecated([{next_iv, '_',"see the 'New and Old API' chapter of the CRYPTO User's guide"}]).
+
+ -deprecated([{'_','_',"use the 'rand' module instead"}]).
+
+ -deprecated_type([{grunka,'_',"use frobnitz/1,2 instead"}]).
+
+You can also use the `Name/Arity` shorthand for all of these variants, e.g.
+`-deprecated([f/1])`, which will result in a generic description to the effect of
+"see the documentation for details."
+
+Note that it is not possible to warn about a module that no longer exists.
+This is to prevent later namespace clashes from raising warnings, like the ones
+we had when the long-removed `net` module reappeared in the socket NIF.
+"Removed" modules should instead have their contents replaced by
+`-removed()` attributes until there's no longer any point in warning
+about their use.
+
+## The DEPRECATIONS file
+
+The [$ERL_TOP/system/doc/general_info/DEPRECATIONS][1] file contains additional
+information about each deprecated function, namely in what release it was deprecated
+and optionally in what release it will be removed. The information in this file will
+be used to generate the [Deprecations](http://erlang.org/doc/general_info/deprecations.html)
+and [Scheduled for Removal](http://erlang.org/doc/general_info/scheduled_for_removal.html)
+pages in the documentation.
+
+Here is how the entry for `erlang:now/0` that was deprecated in OTP 18 looks like:
+
+ erlang:now/0 since=18
+
+Here is an example of a function that was deprecated in OTP 23 and is scheduled for removal in OTP 25:
+
+ filename:safe_relative_path/1 since=23 remove=25
+
+
+ [1]: ../system/doc/general_info/DEPRECATIONS
+ [2]: ../lib/stdlib/src/otp_internal.erl
diff --git a/OTP_VERSION b/OTP_VERSION
index bdec6860fd..f657ed524a 100644
--- a/OTP_VERSION
+++ b/OTP_VERSION
@@ -1 +1 @@
-23.1.5
+23.2.5
diff --git a/bootstrap/bin/no_dot_erlang.boot b/bootstrap/bin/no_dot_erlang.boot
index e7186a3055..607a341cd5 100644
--- a/bootstrap/bin/no_dot_erlang.boot
+++ b/bootstrap/bin/no_dot_erlang.boot
Binary files differ
diff --git a/bootstrap/bin/start.boot b/bootstrap/bin/start.boot
index e7186a3055..607a341cd5 100644
--- a/bootstrap/bin/start.boot
+++ b/bootstrap/bin/start.boot
Binary files differ
diff --git a/bootstrap/bin/start_clean.boot b/bootstrap/bin/start_clean.boot
index e7186a3055..607a341cd5 100644
--- a/bootstrap/bin/start_clean.boot
+++ b/bootstrap/bin/start_clean.boot
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_asm.beam b/bootstrap/lib/compiler/ebin/beam_asm.beam
index 1ef964e83f..e0574f3124 100644
--- a/bootstrap/lib/compiler/ebin/beam_asm.beam
+++ b/bootstrap/lib/compiler/ebin/beam_asm.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_digraph.beam b/bootstrap/lib/compiler/ebin/beam_digraph.beam
index f09e0b3f05..1619595155 100644
--- a/bootstrap/lib/compiler/ebin/beam_digraph.beam
+++ b/bootstrap/lib/compiler/ebin/beam_digraph.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_jump.beam b/bootstrap/lib/compiler/ebin/beam_jump.beam
index e9674e364d..07f1395408 100644
--- a/bootstrap/lib/compiler/ebin/beam_jump.beam
+++ b/bootstrap/lib/compiler/ebin/beam_jump.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_peep.beam b/bootstrap/lib/compiler/ebin/beam_peep.beam
index a1ea4c56d5..57dc52d6fe 100644
--- a/bootstrap/lib/compiler/ebin/beam_peep.beam
+++ b/bootstrap/lib/compiler/ebin/beam_peep.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa.beam b/bootstrap/lib/compiler/ebin/beam_ssa.beam
index bfd5d0152e..ed118c16d6 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_bool.beam b/bootstrap/lib/compiler/ebin/beam_ssa_bool.beam
index ad1f517670..157a77e524 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_bool.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_bool.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_codegen.beam b/bootstrap/lib/compiler/ebin/beam_ssa_codegen.beam
index fec36ab2fa..23bb5192a4 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_codegen.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_codegen.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_dead.beam b/bootstrap/lib/compiler/ebin/beam_ssa_dead.beam
index 0b0ea05b03..cb4ac5122c 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_dead.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_dead.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_opt.beam b/bootstrap/lib/compiler/ebin/beam_ssa_opt.beam
index 3712ff72a1..9c69449771 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_opt.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_opt.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_pp.beam b/bootstrap/lib/compiler/ebin/beam_ssa_pp.beam
index 724c155021..4d8d3e5d6c 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_pp.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_pp.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_pre_codegen.beam b/bootstrap/lib/compiler/ebin/beam_ssa_pre_codegen.beam
index 4ae9121480..f97a03c473 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_pre_codegen.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_pre_codegen.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_recv.beam b/bootstrap/lib/compiler/ebin/beam_ssa_recv.beam
index 9c4080d5e5..4a6997cf07 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_recv.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_recv.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_share.beam b/bootstrap/lib/compiler/ebin/beam_ssa_share.beam
index 96840309aa..a3d7a060ba 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_share.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_share.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_type.beam b/bootstrap/lib/compiler/ebin/beam_ssa_type.beam
index b04b0499ae..514e61b466 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_type.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_type.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_validator.beam b/bootstrap/lib/compiler/ebin/beam_validator.beam
index a705beca10..6b1b04e153 100644
--- a/bootstrap/lib/compiler/ebin/beam_validator.beam
+++ b/bootstrap/lib/compiler/ebin/beam_validator.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/compiler.app b/bootstrap/lib/compiler/ebin/compiler.app
index dd5738c847..bae37d87d1 100644
--- a/bootstrap/lib/compiler/ebin/compiler.app
+++ b/bootstrap/lib/compiler/ebin/compiler.app
@@ -19,7 +19,7 @@
{application, compiler,
[{description, "ERTS CXC 138 10"},
- {vsn, "7.5.4"},
+ {vsn, "7.6.5"},
{modules, [
beam_a,
beam_asm,
@@ -80,5 +80,5 @@
{registered, []},
{applications, [kernel, stdlib]},
{env, []},
- {runtime_dependencies, ["stdlib-@OTP-15251@","kernel-@OTP-15251@","hipe-3.12","erts-@OTP-15251@",
+ {runtime_dependencies, ["stdlib-3.13","kernel-7.0","hipe-3.12","erts-11.0",
"crypto-3.6"]}]}.
diff --git a/bootstrap/lib/compiler/ebin/core_parse.beam b/bootstrap/lib/compiler/ebin/core_parse.beam
index a595b4b7c1..6a60b958b4 100644
--- a/bootstrap/lib/compiler/ebin/core_parse.beam
+++ b/bootstrap/lib/compiler/ebin/core_parse.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/erl_bifs.beam b/bootstrap/lib/compiler/ebin/erl_bifs.beam
index 06e497b1d2..4d1b788bd9 100644
--- a/bootstrap/lib/compiler/ebin/erl_bifs.beam
+++ b/bootstrap/lib/compiler/ebin/erl_bifs.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/sys_core_alias.beam b/bootstrap/lib/compiler/ebin/sys_core_alias.beam
index a7e2c47364..7f88d7bb9b 100644
--- a/bootstrap/lib/compiler/ebin/sys_core_alias.beam
+++ b/bootstrap/lib/compiler/ebin/sys_core_alias.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/v3_core.beam b/bootstrap/lib/compiler/ebin/v3_core.beam
index a256327666..3d0c23d772 100644
--- a/bootstrap/lib/compiler/ebin/v3_core.beam
+++ b/bootstrap/lib/compiler/ebin/v3_core.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/v3_kernel.beam b/bootstrap/lib/compiler/ebin/v3_kernel.beam
index 1e161305c9..a0fe4a3775 100644
--- a/bootstrap/lib/compiler/ebin/v3_kernel.beam
+++ b/bootstrap/lib/compiler/ebin/v3_kernel.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/disk_log.beam b/bootstrap/lib/kernel/ebin/disk_log.beam
index 6f9e212438..fd3a3309b6 100644
--- a/bootstrap/lib/kernel/ebin/disk_log.beam
+++ b/bootstrap/lib/kernel/ebin/disk_log.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/erl_epmd.beam b/bootstrap/lib/kernel/ebin/erl_epmd.beam
index 28c1a2428f..0a9b03732e 100644
--- a/bootstrap/lib/kernel/ebin/erl_epmd.beam
+++ b/bootstrap/lib/kernel/ebin/erl_epmd.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/file.beam b/bootstrap/lib/kernel/ebin/file.beam
index 1e12563771..18006a9cc4 100644
--- a/bootstrap/lib/kernel/ebin/file.beam
+++ b/bootstrap/lib/kernel/ebin/file.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/gen_tcp_socket.beam b/bootstrap/lib/kernel/ebin/gen_tcp_socket.beam
index 7793e1eca9..3cbec1359e 100644
--- a/bootstrap/lib/kernel/ebin/gen_tcp_socket.beam
+++ b/bootstrap/lib/kernel/ebin/gen_tcp_socket.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/global.beam b/bootstrap/lib/kernel/ebin/global.beam
index 57bb34e2a5..12a33d895b 100644
--- a/bootstrap/lib/kernel/ebin/global.beam
+++ b/bootstrap/lib/kernel/ebin/global.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/hipe_unified_loader.beam b/bootstrap/lib/kernel/ebin/hipe_unified_loader.beam
index 45c54199e1..1c2262a7a5 100644
--- a/bootstrap/lib/kernel/ebin/hipe_unified_loader.beam
+++ b/bootstrap/lib/kernel/ebin/hipe_unified_loader.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet_db.beam b/bootstrap/lib/kernel/ebin/inet_db.beam
index d5bac62aed..35df8327d7 100644
--- a/bootstrap/lib/kernel/ebin/inet_db.beam
+++ b/bootstrap/lib/kernel/ebin/inet_db.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet_res.beam b/bootstrap/lib/kernel/ebin/inet_res.beam
index d7dc10c484..fe972672aa 100644
--- a/bootstrap/lib/kernel/ebin/inet_res.beam
+++ b/bootstrap/lib/kernel/ebin/inet_res.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet_tcp_dist.beam b/bootstrap/lib/kernel/ebin/inet_tcp_dist.beam
index b6211f3538..496fc213f0 100644
--- a/bootstrap/lib/kernel/ebin/inet_tcp_dist.beam
+++ b/bootstrap/lib/kernel/ebin/inet_tcp_dist.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/kernel.app b/bootstrap/lib/kernel/ebin/kernel.app
index 7288d0e0ba..80f68e62d6 100644
--- a/bootstrap/lib/kernel/ebin/kernel.app
+++ b/bootstrap/lib/kernel/ebin/kernel.app
@@ -22,7 +22,7 @@
{application, kernel,
[
{description, "ERTS CXC 138 10"},
- {vsn, "6.5.2"},
+ {vsn, "7.1"},
{modules, [application,
application_controller,
application_master,
@@ -155,6 +155,6 @@
{shell_docs_ansi,auto}
]},
{mod, {kernel, []}},
- {runtime_dependencies, ["erts-@OTP-15251@", "stdlib-@OTP-15251@", "sasl-3.0"]}
+ {runtime_dependencies, ["erts-11.0", "stdlib-3.13", "sasl-3.0"]}
]
}.
diff --git a/bootstrap/lib/kernel/ebin/kernel_refc.beam b/bootstrap/lib/kernel/ebin/kernel_refc.beam
index fa2e54d616..0da8237b7f 100644
--- a/bootstrap/lib/kernel/ebin/kernel_refc.beam
+++ b/bootstrap/lib/kernel/ebin/kernel_refc.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/logger_formatter.beam b/bootstrap/lib/kernel/ebin/logger_formatter.beam
index b9b82075ca..eb3ddf6ab9 100644
--- a/bootstrap/lib/kernel/ebin/logger_formatter.beam
+++ b/bootstrap/lib/kernel/ebin/logger_formatter.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/logger_olp.beam b/bootstrap/lib/kernel/ebin/logger_olp.beam
index ee1baed8c2..bbeff97faf 100644
--- a/bootstrap/lib/kernel/ebin/logger_olp.beam
+++ b/bootstrap/lib/kernel/ebin/logger_olp.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/logger_proxy.beam b/bootstrap/lib/kernel/ebin/logger_proxy.beam
index fbfb960705..24222341cb 100644
--- a/bootstrap/lib/kernel/ebin/logger_proxy.beam
+++ b/bootstrap/lib/kernel/ebin/logger_proxy.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/logger_simple_h.beam b/bootstrap/lib/kernel/ebin/logger_simple_h.beam
index ee0a4ffdf2..a624bdd45f 100644
--- a/bootstrap/lib/kernel/ebin/logger_simple_h.beam
+++ b/bootstrap/lib/kernel/ebin/logger_simple_h.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/logger_std_h.beam b/bootstrap/lib/kernel/ebin/logger_std_h.beam
index 8f0adc65a3..b9644e5e2b 100644
--- a/bootstrap/lib/kernel/ebin/logger_std_h.beam
+++ b/bootstrap/lib/kernel/ebin/logger_std_h.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/net_kernel.beam b/bootstrap/lib/kernel/ebin/net_kernel.beam
index 95deb12ff3..e71934422f 100644
--- a/bootstrap/lib/kernel/ebin/net_kernel.beam
+++ b/bootstrap/lib/kernel/ebin/net_kernel.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/pg.beam b/bootstrap/lib/kernel/ebin/pg.beam
index 9a0018ee63..9375c68a46 100644
--- a/bootstrap/lib/kernel/ebin/pg.beam
+++ b/bootstrap/lib/kernel/ebin/pg.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/raw_file_io_deflate.beam b/bootstrap/lib/kernel/ebin/raw_file_io_deflate.beam
index 0bab0a081d..f3504c5c9f 100644
--- a/bootstrap/lib/kernel/ebin/raw_file_io_deflate.beam
+++ b/bootstrap/lib/kernel/ebin/raw_file_io_deflate.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/socket.beam b/bootstrap/lib/kernel/ebin/socket.beam
index e7af0b892b..051885341c 100644
--- a/bootstrap/lib/kernel/ebin/socket.beam
+++ b/bootstrap/lib/kernel/ebin/socket.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/user_drv.beam b/bootstrap/lib/kernel/ebin/user_drv.beam
index cff059790e..8a598eff84 100644
--- a/bootstrap/lib/kernel/ebin/user_drv.beam
+++ b/bootstrap/lib/kernel/ebin/user_drv.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/include/logger.hrl b/bootstrap/lib/kernel/include/logger.hrl
index b09977e0f2..bd17f7efc4 100644
--- a/bootstrap/lib/kernel/include/logger.hrl
+++ b/bootstrap/lib/kernel/include/logger.hrl
@@ -46,7 +46,7 @@
-define(DO_LOG(Level,Args),
case logger:allow(Level,?MODULE) of
true ->
- apply(logger,macro_log,[?LOCATION,Level|Args]);
+ erlang:apply(logger,macro_log,[?LOCATION,Level|Args]);
false ->
ok
end).
diff --git a/bootstrap/lib/stdlib/ebin/digraph.beam b/bootstrap/lib/stdlib/ebin/digraph.beam
index b28fe8aae1..f9a7d68504 100644
--- a/bootstrap/lib/stdlib/ebin/digraph.beam
+++ b/bootstrap/lib/stdlib/ebin/digraph.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/epp.beam b/bootstrap/lib/stdlib/ebin/epp.beam
index fe630ad03d..43050c392e 100644
--- a/bootstrap/lib/stdlib/ebin/epp.beam
+++ b/bootstrap/lib/stdlib/ebin/epp.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_eval.beam b/bootstrap/lib/stdlib/ebin/erl_eval.beam
index 6e346923fa..b62581e20a 100644
--- a/bootstrap/lib/stdlib/ebin/erl_eval.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_eval.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_parse.beam b/bootstrap/lib/stdlib/ebin/erl_parse.beam
index 1f7680b6b2..5eddd13cfc 100644
--- a/bootstrap/lib/stdlib/ebin/erl_parse.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_parse.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/error_logger_file_h.beam b/bootstrap/lib/stdlib/ebin/error_logger_file_h.beam
index ef98fc4293..a3efb7f04b 100644
--- a/bootstrap/lib/stdlib/ebin/error_logger_file_h.beam
+++ b/bootstrap/lib/stdlib/ebin/error_logger_file_h.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/error_logger_tty_h.beam b/bootstrap/lib/stdlib/ebin/error_logger_tty_h.beam
index 5326c31d44..d6d5ad85ab 100644
--- a/bootstrap/lib/stdlib/ebin/error_logger_tty_h.beam
+++ b/bootstrap/lib/stdlib/ebin/error_logger_tty_h.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/filelib.beam b/bootstrap/lib/stdlib/ebin/filelib.beam
index 389dbd012a..06d958a89b 100644
--- a/bootstrap/lib/stdlib/ebin/filelib.beam
+++ b/bootstrap/lib/stdlib/ebin/filelib.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/gen_server.beam b/bootstrap/lib/stdlib/ebin/gen_server.beam
index b697a060e5..55b21213b9 100644
--- a/bootstrap/lib/stdlib/ebin/gen_server.beam
+++ b/bootstrap/lib/stdlib/ebin/gen_server.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/gen_statem.beam b/bootstrap/lib/stdlib/ebin/gen_statem.beam
index e226e9cf3a..60dc5eab0d 100644
--- a/bootstrap/lib/stdlib/ebin/gen_statem.beam
+++ b/bootstrap/lib/stdlib/ebin/gen_statem.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/io_lib_format.beam b/bootstrap/lib/stdlib/ebin/io_lib_format.beam
index daed34b0d3..9925476691 100644
--- a/bootstrap/lib/stdlib/ebin/io_lib_format.beam
+++ b/bootstrap/lib/stdlib/ebin/io_lib_format.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/lists.beam b/bootstrap/lib/stdlib/ebin/lists.beam
index 233b63982e..722d493bf6 100644
--- a/bootstrap/lib/stdlib/ebin/lists.beam
+++ b/bootstrap/lib/stdlib/ebin/lists.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/maps.beam b/bootstrap/lib/stdlib/ebin/maps.beam
index a5c10ae33a..731e2418ea 100644
--- a/bootstrap/lib/stdlib/ebin/maps.beam
+++ b/bootstrap/lib/stdlib/ebin/maps.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/otp_internal.beam b/bootstrap/lib/stdlib/ebin/otp_internal.beam
index 547f105df8..9ef9945d43 100644
--- a/bootstrap/lib/stdlib/ebin/otp_internal.beam
+++ b/bootstrap/lib/stdlib/ebin/otp_internal.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/proplists.beam b/bootstrap/lib/stdlib/ebin/proplists.beam
index 85f8f0af36..a0acbdb212 100644
--- a/bootstrap/lib/stdlib/ebin/proplists.beam
+++ b/bootstrap/lib/stdlib/ebin/proplists.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/shell.beam b/bootstrap/lib/stdlib/ebin/shell.beam
index 82dcca6255..3135e8ca2e 100644
--- a/bootstrap/lib/stdlib/ebin/shell.beam
+++ b/bootstrap/lib/stdlib/ebin/shell.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/shell_docs.beam b/bootstrap/lib/stdlib/ebin/shell_docs.beam
index 658e97a4e7..d539010453 100644
--- a/bootstrap/lib/stdlib/ebin/shell_docs.beam
+++ b/bootstrap/lib/stdlib/ebin/shell_docs.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/stdlib.app b/bootstrap/lib/stdlib/ebin/stdlib.app
index 26d27e9986..62ad6044ba 100644
--- a/bootstrap/lib/stdlib/ebin/stdlib.app
+++ b/bootstrap/lib/stdlib/ebin/stdlib.app
@@ -20,7 +20,7 @@
%%
{application, stdlib,
[{description, "ERTS CXC 138 10"},
- {vsn, "3.12.1"},
+ {vsn, "3.13.2"},
{modules, [array,
base64,
beam_lib,
@@ -109,6 +109,6 @@
dets]},
{applications, [kernel]},
{env, []},
- {runtime_dependencies, ["sasl-3.0","kernel-@OTP-15251@","erts-@OTP-15251:OTP-16431@","crypto-3.3",
+ {runtime_dependencies, ["sasl-3.0","kernel-7.0","erts-11.0","crypto-3.3",
"compiler-5.0"]}
]}.
diff --git a/bootstrap/lib/stdlib/ebin/supervisor.beam b/bootstrap/lib/stdlib/ebin/supervisor.beam
index 42d19e46f3..263f3efc13 100644
--- a/bootstrap/lib/stdlib/ebin/supervisor.beam
+++ b/bootstrap/lib/stdlib/ebin/supervisor.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/uri_string.beam b/bootstrap/lib/stdlib/ebin/uri_string.beam
index 1557c34f51..b482af410d 100644
--- a/bootstrap/lib/stdlib/ebin/uri_string.beam
+++ b/bootstrap/lib/stdlib/ebin/uri_string.beam
Binary files differ
diff --git a/erts/configure.in b/erts/configure.in
index 308e1288ba..12dfb9b19b 100644
--- a/erts/configure.in
+++ b/erts/configure.in
@@ -1903,8 +1903,9 @@ fi
if test "x$ac_compiler_gnu" = "xyes"; then
AC_MSG_CHECKING([if we should add -fno-tree-copyrename to CFLAGS for computed gotos to work properly])
+## tree-copyrename was broken in gcc 4.3 and then removed in gcc 6
AC_TRY_COMPILE([],[
- #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)
+ #if (__GNUC__ > 4 && __GNUC__ < 6) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)
;
#else
#error old and ok
diff --git a/erts/doc/src/erl_cmd.xml b/erts/doc/src/erl_cmd.xml
index 91954b9e7a..eb1e9e2de7 100644
--- a/erts/doc/src/erl_cmd.xml
+++ b/erts/doc/src/erl_cmd.xml
@@ -678,7 +678,7 @@
produces a crash dump. On Unix systems, sending an emulator process
a <c>SIGUSR1</c> signal also forces a crash dump.</p>
</item>
- <tag><marker id="+dcg"/><c><![CDATA[+rg DecentralizedCounterGroupsLimit]]></c></tag>
+ <tag><marker id="+dcg"/><c><![CDATA[+dcg DecentralizedCounterGroupsLimit]]></c></tag>
<item>
<p>Limits the number of decentralized counter groups used by
decentralized counters optimized for update operations in the
@@ -865,11 +865,6 @@
<c>+IOt</c> are used, <c>+IOPt</c> is ignored.
</p>
</item>
- <tag><c><![CDATA[+l]]></c></tag>
- <item>
- <p>Enables autoload tracing, displaying information while loading
- code.</p>
- </item>
<tag><c><![CDATA[+L]]></c></tag>
<item>
<p>Prevents loading information about source filenames and line
diff --git a/erts/doc/src/erl_nif.xml b/erts/doc/src/erl_nif.xml
index bcc7bc3e93..d2a1581e35 100644
--- a/erts/doc/src/erl_nif.xml
+++ b/erts/doc/src/erl_nif.xml
@@ -660,7 +660,7 @@ int writeiovec(ErlNifEnv *env, ERL_NIF_TERM term, ERL_NIF_TERM *tail,
it can only be passed on to API functions. Three types of environments
exist:</p>
<taglist>
- <tag>Process bound environment</tag>
+ <tag><marker id="proc_bound_env"/>Process bound environment</tag>
<item>
<p>Passed as the first argument to all NIFs. All function arguments
passed to a NIF belong to that environment. The return value from
@@ -671,7 +671,7 @@ int writeiovec(ErlNifEnv *env, ERL_NIF_TERM term, ERL_NIF_TERM *tail,
returns. It is thus useless and dangerous to store pointers to
process bound environments between NIF calls.</p>
</item>
- <tag>Callback environment</tag>
+ <tag><marker id="callback_env"/>Callback environment</tag>
<item>
<p>Passed as the first argument to all the non-NIF callback functions
(<seecref marker="#load"><c>load</c></seecref>,
@@ -685,7 +685,7 @@ int writeiovec(ErlNifEnv *env, ERL_NIF_TERM term, ERL_NIF_TERM *tail,
returned. Terms may be created in this environment but they will
only be accessible during the callback.</p>
</item>
- <tag>Process independent environment</tag>
+ <tag><marker id="proc_indep_env"/>Process independent environment</tag>
<item>
<p>Created by calling <seecref marker="#enif_alloc_env">
<c>enif_alloc_env</c></seecref>. This environment can be
@@ -1006,7 +1006,9 @@ typedef struct {
<name since="OTP R14B"><ret>ErlNifEnv *</ret><nametext>enif_alloc_env()</nametext></name>
<fsummary>Create a new environment.</fsummary>
<desc>
- <p>Allocates a new process independent environment. The environment can
+ <p>
+ Allocates a new <seecref marker="#proc_indep_env">process
+ independent environment</seecref>. The environment can
be used to hold terms that are not bound to any process. Such terms
can later be copied to a process environment with
<seecref marker="#enif_make_copy"><c>enif_make_copy</c></seecref> or
@@ -1253,8 +1255,12 @@ typedef struct {
<c>enif_monitor_process</c></seecref>. Argument <c>obj</c> is a pointer
to the resource holding the monitor and <c>*mon</c> identifies the
monitor.</p>
- <p>Argument <c>caller_env</c> is the environment of the calling process
- or callback. Must only be NULL if calling from a custom thread.</p>
+ <p>
+ Argument <c>caller_env</c> is the environment of the calling thread
+ (<seecref marker="#proc_bound_env">process bound</seecref> or
+ <seecref marker="#callback_env">callback</seecref> environment) or
+ <c>NULL</c> if calling from a custom thread not spawned by ERTS.
+ </p>
<p>Returns <c>0</c> if the monitor was successfully identified and removed.
Returns a non-zero value if the monitor could not be identified, which means
it was either</p>
@@ -2659,8 +2665,12 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
<seecref marker="#enif_compare_monitors"><c>enif_compare_monitors</c></seecref>.
A monitor is automatically removed when it triggers or when
the resource is deallocated.</p>
- <p>Argument <c>caller_env</c> is the environment of the calling process
- or callback. Must only be NULL if calling from a custom thread.</p>
+ <p>
+ Argument <c>caller_env</c> is the environment of the calling thread
+ (<seecref marker="#proc_bound_env">process bound</seecref> or
+ <seecref marker="#callback_env">callback</seecref> environment) or
+ <c>NULL</c> if calling from a custom thread not spawned by ERTS.
+ </p>
<p>Returns <c>0</c> on success, &lt; 0 if no <c>down</c> callback is
provided, and &gt; 0 if the process is no longer alive or if
<c>target_pid</c> is <seecref marker="#enif_set_pid_undefined">
@@ -3263,7 +3273,7 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
<p>Initializes the <seecref marker="#ErlNifPid"><c>ErlNifPid</c></seecref>
variable at <c>*pid</c> to represent the calling process.</p>
<p>Returns <c>pid</c> if successful, or NULL if <c>caller_env</c> is not
- a <seecref marker="#ErlNifEnv">process bound environment</seecref>.</p>
+ a <seecref marker="#proc_bound_env">process bound environment</seecref>.</p>
</desc>
</func>
@@ -3275,8 +3285,12 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
<p>Sends a message to a process.</p>
<taglist>
<tag><c>caller_env</c></tag>
- <item>The environment of the calling process or callback. Must be <c>NULL</c>
- only if calling from a custom thread not spawned by ERTS.</item>
+ <item>
+ The environment of the calling thread
+ (<seecref marker="#proc_bound_env">process bound</seecref> or
+ <seecref marker="#callback_env">callback</seecref> environment) or
+ <c>NULL</c> if calling from a custom thread not spawned by ERTS.
+ </item>
<tag><c>*to_pid</c></tag>
<item>The pid of the receiving process. The pid is to refer to a
process on the local node.</item>
@@ -3604,15 +3618,18 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
<func>
<name since="OTP 20.0"><ret>int</ret>
- <nametext>enif_whereis_pid(ErlNifEnv *env,
+ <nametext>enif_whereis_pid(ErlNifEnv *caller_env,
ERL_NIF_TERM name, ErlNifPid *pid)</nametext></name>
<fsummary>Looks up a process by its registered name.</fsummary>
<desc>
<p>Looks up a process by its registered name.</p>
<taglist>
- <tag><c>env</c></tag>
- <item>The environment of the calling process. Must be <c>NULL</c>
- only if calling from a created thread.</item>
+ <tag><c>caller_env</c></tag>
+ <item>The environment of the calling thread
+ (<seecref marker="#proc_bound_env">process bound</seecref> or
+ <seecref marker="#callback_env">callback</seecref> environment) or
+ <c>NULL</c> if calling from a custom thread not spawned by
+ ERTS.</item>
<tag><c>name</c></tag>
<item>The name of a registered process, as an atom.</item>
<tag><c>*pid</c></tag>
@@ -3632,15 +3649,18 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
<func>
<name since="OTP 20.0"><ret>int</ret>
- <nametext>enif_whereis_port(ErlNifEnv *env,
+ <nametext>enif_whereis_port(ErlNifEnv *caller_env,
ERL_NIF_TERM name, ErlNifPort *port)</nametext></name>
<fsummary>Looks up a port by its registered name.</fsummary>
<desc>
<p>Looks up a port by its registered name.</p>
<taglist>
- <tag><c>env</c></tag>
- <item>The environment of the calling process. Must be <c>NULL</c>
- only if calling from a created thread.</item>
+ <tag><c>caller_env</c></tag>
+ <item>The environment of the calling thread
+ (<seecref marker="#proc_bound_env">process bound</seecref> or
+ <seecref marker="#callback_env">callback</seecref> environment) or
+ <c>NULL</c> if calling from a custom thread not spawned by
+ ERTS.</item>
<tag><c>name</c></tag>
<item>The name of a registered port, as an atom.</item>
<tag><c>*port</c></tag>
diff --git a/erts/doc/src/escript_cmd.xml b/erts/doc/src/escript_cmd.xml
index 47d18c0872..b234b6752c 100644
--- a/erts/doc/src/escript_cmd.xml
+++ b/erts/doc/src/escript_cmd.xml
@@ -42,7 +42,7 @@
the top <c>bin</c> directory of the standalone system and given
<c>.escript</c> as file extension. Further the (built-in)
<c>escript</c> program should be copied to the same directory and
- given the scripts original name (without the <c>.escript</c>
+ given the script's original name (without the <c>.escript</c>
extension). This will enable use of the bundled Erlang runtime
system.</p>
@@ -101,7 +101,7 @@ usage: factorial integer</pre>
a normal Erlang module. The first line is intended to be the
interpreter line, which invokes <c>escript</c>.</p>
<p>However, if you invoke the <c>escript</c> as follows,
- the contents of the first line does not matter, but it
+ the contents of the first line do not matter, but it
cannot contain Erlang code as it will be ignored:</p>
<pre>
$ <input>escript factorial 5</input></pre>
diff --git a/erts/doc/src/inet_cfg.xml b/erts/doc/src/inet_cfg.xml
index 0e2437e33c..7934a6df86 100644
--- a/erts/doc/src/inet_cfg.xml
+++ b/erts/doc/src/inet_cfg.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2004</year><year>2016</year>
+ <year>2004</year><year>2020</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -268,6 +268,17 @@
<seeerl marker="kernel:inet_res"><c>inet_res(3)</c></seeerl>
will try before giving up. Defaults to 3.</p>
</item>
+
+ <tag><c><![CDATA[{servfail_retry_timeout, Time}.]]></c></tag>
+ <item>
+ <p><c><![CDATA[Time = non_neg_integer()]]></c></p>
+ <p>After all name servers have been tried, there is a timeout
+ before the name servers are tried again. This is to prevent
+ the server from answering the query with what's in the servfail cache,
+ <seeerl marker="kernel:inet_res#servfail_retry_timeout"><c>inet_res(3)</c></seeerl>.
+ Defaults to 1500 milli seconds .</p>
+ </item>
+
<tag><c><![CDATA[{inet6, Bool}.]]></c></tag>
<item>
<p><c><![CDATA[Bool = true | false]]></c></p>
diff --git a/erts/doc/src/notes.xml b/erts/doc/src/notes.xml
index 25b16d6681..29f33ba2d8 100644
--- a/erts/doc/src/notes.xml
+++ b/erts/doc/src/notes.xml
@@ -31,6 +31,199 @@
</header>
<p>This document describes the changes made to the ERTS application.</p>
+<section><title>Erts 11.1.8</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Fixed a bug that could cause some work scheduled for
+ execution on scheduler threads to be delayed until other
+ similar work appeared. Beside delaying various cleanup of
+ internal data structures also the following could be
+ delayed:</p> <list> <item>Termination of a distribution
+ controller process</item> <item>Disabling of the
+ distribution on a node</item> <item>Gathering of memory
+ allocator information using the <c>instrument</c>
+ module</item> <item>Enabling, disabling, and gathering of
+ <c>msacc</c> information</item> <item>Delivery of
+ <c>'CHANGE'</c> messages when time offset is
+ monitored</item> <item>A call to
+ <c>erlang:cancel_timer()</c></item> <item>A call to
+ <c>erlang:read_timer()</c></item> <item>A call to
+ <c>erlang:statistics(io | garbage_collection |
+ scheduler_wall_time)</c></item> <item>A call to
+ <c>ets:all()</c></item> <item>A call to
+ <c>erlang:memory()</c></item> <item>A call to
+ <c>erlang:system_info({allocator | allocator_sizes,
+ _})</c></item> <item>A call to
+ <c>erlang:trace_delivered()</c></item> </list> <p>The bug
+ existed on runtime systems running on all types of
+ hardware except for x86/x86_64.</p>
+ <p>
+ Own Id: OTP-17185</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 11.1.7</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Make windows installer remove write access rights for non
+ admin users when installing to a non default directory.
+ Reduces the risk for DLL sideloading, but the user should
+ always be aware of the access rights for the
+ installation.</p>
+ <p>
+ Own Id: OTP-17097</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 11.1.6</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The <c>suspend_process()</c> and <c>resume_process()</c>
+ BIFs did not check their arguments properly which could
+ cause an emulator crash.</p>
+ <p>
+ Own Id: OTP-17080</p>
+ </item>
+ <item>
+ <p>
+ The runtime system would get into an infinite loop if the
+ runtime system was started with more than 1023 file
+ descriptors already open.</p>
+ <p>
+ Own Id: OTP-17088 Aux Id: ERIERL-580 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 11.1.5</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix emulator crash when sending small bit-strings over
+ Erlang distribution while the connection is being setup.</p>
+ <p>
+ The fault was introduced in OTP-23.0</p>
+ <p>
+ Own Id: OTP-17083 Aux Id: ERIERL-572 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 11.1.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed bug which could cause VM crash when a NIF is loaded
+ at the same time as the Erlang implementation of the NIF
+ is called. Bug exists since OTP 23.0.</p>
+ <p>
+ Own Id: OTP-16859</p>
+ </item>
+ <item>
+ <p>
+ Fixed <c>enif_make_map_*</c> functions in debug build
+ when given environment from <c>enif_alloc_env</c>.</p>
+ <p>
+ Own Id: OTP-16863 Aux Id: ERL-1352 </p>
+ </item>
+ <item>
+ <p>
+ Fixed broken configuration option <c>--disable-pie</c>.</p>
+ <p>
+ Own Id: OTP-16864</p>
+ </item>
+ <item>
+ <p>
+ Fixed rare distribution bug in race between received
+ signal (link/monitor/spawn_request/spawn_reply) and
+ disconnection. Symptom: VM crash. Since: OTP 21.0.</p>
+ <p>
+ Own Id: OTP-16869 Aux Id: ERL-1337 </p>
+ </item>
+ <item>
+ <p>Fixed a performance issue when extremely many items
+ were stored in the process dictionary. (Fixing this bug
+ also eliminates a compiler warning emitted by the latest
+ version of Clang.)</p>
+ <p>
+ Own Id: OTP-16888</p>
+ </item>
+ <item>
+ <p>
+ Remove <c>-ftree-copyrename</c> from flags passed to
+ compiler when building erts. The flag is not used by
+ modern gcc's and is not supported by clang.</p>
+ <p>
+ Own Id: OTP-16894</p>
+ </item>
+ <item>
+ <p>Modules using complicated nested binary comprehensions
+ could fail to load.</p>
+ <p>
+ Own Id: OTP-16899</p>
+ </item>
+ <item>
+ <p>Fixed a race in <c>file:read_file/1</c> were an
+ incomplete file could be returned if another OS process
+ swapped the file out while reading.</p>
+ <p>
+ Own Id: OTP-16948 Aux Id: PR-2792 </p>
+ </item>
+ <item>
+ <p>The call <c>list_to_integer("10", true)</c> would
+ return <c>4</c> instead of raising an exception. Certain
+ other atoms would also be interpreted as a number
+ base.</p>
+ <p>
+ Own Id: OTP-17030</p>
+ </item>
+ <item>
+ <p>
+ On macOS 11 (Big Sur), erl would not start if the maximum
+ number of file descriptors were unlimited (<c>ulimit -n
+ unlimited</c>).</p>
+ <p>
+ Own Id: OTP-17055 Aux Id: ERL-1417 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Add manifest to all executables and dynamic libraries.</p>
+ <p>
+ Own Id: OTP-17067 Aux Id: PR-2907 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Erts 11.1.3</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -1023,6 +1216,118 @@
</section>
+<section><title>Erts 10.7.2.6</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed rare distribution bug in race between received
+ signal (link/monitor/spawn_request/spawn_reply) and
+ disconnection. Symptom: VM crash. Since: OTP 21.0.</p>
+ <p>
+ Own Id: OTP-16869 Aux Id: ERL-1337 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 10.7.2.5</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed bugs causing issues when enabling the ERTS internal
+ allocators on a system built with the undocumented and
+ unsupported <c>SMALL_MEMORY</c> feature.</p>
+ <p>
+ Own Id: OTP-16939</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 10.7.2.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ inet:setopts([{active,once}]) wakes up IO polling thread
+ unnecessarily, leading to lock contention and visibly
+ higher CPU utilization.</p>
+ <p>
+ Own Id: OTP-16847 Aux Id: ERL-1301 </p>
+ </item>
+ <item>
+ <p>
+ The documentation of <c>statistics(run_queue)</c>
+ erroneously stated that it returns the total length of
+ all normal run queues when it is the total length of all
+ normal and dirty CPU run queues that is returned. The
+ documentation has been updated to reflect the actual
+ behavior.</p>
+ <p>
+ Own Id: OTP-16866 Aux Id: ERL-1355 </p>
+ </item>
+ <item>
+ <p>
+ Two bugs in the ERTS internal thread wakeup functionality
+ have been fixed. These bugs mainly hit when all threads
+ in the system tried to go to sleep. When the bugs were
+ triggered, certain operations were delayed until a thread
+ woke up due to some other reason. Most important
+ operations effected were code loading, persistent term
+ updates, and memory deallocation.</p>
+ <p>
+ Own Id: OTP-16870</p>
+ </item>
+ <item>
+ <p>
+ Fixed bug in <c>ets:select_replace/2</c> on
+ <c>compressed</c> tables that could produce faulty
+ results or VM crash. Bug exists since OTP 20.</p>
+ <p>
+ Own Id: OTP-16874 Aux Id: ERL-1356, PR-2763 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ As of OTP 22, the allocator specific memory carrier pools
+ were replaced by a node global carrier pool. This
+ unfortunately caused substantial memory fragmentation in
+ some cases due to long lived data being spread into
+ carriers used by allocators mainly handling short lived
+ data.</p>
+ <p>
+ A new command line argument <c>+M&lt;S&gt;cp</c> has been
+ introduced with which one can enable the old behavior as
+ well as configuring other behaviors for the carrier
+ pools. In order to configure the old behavior, with
+ allocator specific carrier pools for all allocators, pass
+ <c>+Mucp :</c> (including the colon character) as a
+ command line argument to <c>erl</c> when starting the
+ Erlang system.</p>
+ <p>
+ The default configuration for carrier pools will be
+ changed to <c>+Mucp :</c> some time in the future, but
+ not in this patch.</p>
+ <p>
+ Own Id: OTP-16856</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Erts 10.7.2.3</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -2870,6 +3175,53 @@
</section>
+<section><title>Erts 10.3.5.14</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The ERTS internal I/O poll implementation could get into
+ an inconsistent state causing input events to be ignored.</p>
+ <p>
+ Own Id: OTP-16780 Aux Id: PR-2701 </p>
+ </item>
+ <item>
+ <p>
+ The documentation of <c>statistics(run_queue)</c>
+ erroneously stated that it returns the total length of
+ all normal run queues when it is the total length of all
+ normal and dirty CPU run queues that is returned. The
+ documentation has been updated to reflect the actual
+ behavior.</p>
+ <p>
+ Own Id: OTP-16866 Aux Id: ERL-1355 </p>
+ </item>
+ <item>
+ <p>
+ Two bugs in the ERTS internal thread wakeup functionality
+ have been fixed. These bugs mainly hit when all threads
+ in the system tried to go to sleep. When the bugs were
+ triggered, certain operations were delayed until a thread
+ woke up due to some other reason. Most important
+ operations effected were code loading, persistent term
+ updates, and memory deallocation.</p>
+ <p>
+ Own Id: OTP-16870</p>
+ </item>
+ <item>
+ <p>
+ Fixed bug in <c>ets:select_replace/2</c> on
+ <c>compressed</c> tables that could produce faulty
+ results or VM crash. Bug exists since OTP 20.</p>
+ <p>
+ Own Id: OTP-16874 Aux Id: ERL-1356, PR-2763 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Erts 10.3.5.13</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/erts/emulator/beam/bif.c b/erts/emulator/beam/bif.c
index 5beab81b98..4f1f114eba 100644
--- a/erts/emulator/beam/bif.c
+++ b/erts/emulator/beam/bif.c
@@ -3105,13 +3105,15 @@ BIF_RETTYPE list_to_integer_2(BIF_ALIST_2)
int base;
i = erts_list_length(BIF_ARG_1);
- if (i < 0)
- BIF_ERROR(BIF_P, BADARG);
-
+ if (i < 0 || is_not_small(BIF_ARG_2)) {
+ BIF_ERROR(BIF_P, BADARG);
+ }
+
base = signed_val(BIF_ARG_2);
- if (base < 2 || base > 36)
- BIF_ERROR(BIF_P, BADARG);
+ if (base < 2 || base > 36) {
+ BIF_ERROR(BIF_P, BADARG);
+ }
if (erts_list_to_integer(BIF_P, BIF_ARG_1, base,
&res, &dummy) != LTI_ALL_INTEGER) {
diff --git a/erts/emulator/beam/dist.c b/erts/emulator/beam/dist.c
index 3261fbb5b8..945764d552 100644
--- a/erts/emulator/beam/dist.c
+++ b/erts/emulator/beam/dist.c
@@ -2016,21 +2016,27 @@ int erts_net_message(Port *prt,
from, to);
ASSERT(ldp->a.other.item == to);
ASSERT(eq(ldp->b.other.item, from));
- code = erts_link_dist_insert(&ldp->a, dep->mld);
- ASSERT(code);
- if (erts_proc_sig_send_link(NULL, to, &ldp->b))
- break; /* done */
+ code = erts_link_dist_insert(&ldp->a, ede.mld);
+ if (erts_proc_sig_send_link(NULL, to, &ldp->b)) {
+ if (!code) {
+ /* Race: connection already down => send link exit */
+ erts_proc_sig_send_link_exit(NULL, THE_NON_VALUE, &ldp->a,
+ am_noconnection, NIL);
+ }
+ break; /* Done */
+ }
/* Failed to send signal; cleanup and reply noproc... */
-
- code = erts_link_dist_delete(&ldp->a);
- ASSERT(code);
+ if (code) {
+ code = erts_link_dist_delete(&ldp->a);
+ ASSERT(code);
+ }
erts_link_release_both(ldp);
}
code = erts_dsig_prepare(&ctx, dep, NULL, 0, ERTS_DSP_NO_LOCK, 1, 1, 0);
- if (code == ERTS_DSIG_PREP_CONNECTED) {
+ if (code == ERTS_DSIG_PREP_CONNECTED && ctx.connection_id == conn_id) {
code = erts_dsig_send_exit(&ctx, to, from, am_noproc);
ASSERT(code == ERTS_DSIG_SEND_OK);
}
@@ -2105,8 +2111,11 @@ int erts_net_message(Port *prt,
mdp = erts_monitor_create(ERTS_MON_TYPE_DIST_PROC,
ref, watcher, pid, name);
- code = erts_monitor_dist_insert(&mdp->origin, dep->mld);
- ASSERT(code); (void)code;
+ if (!erts_monitor_dist_insert(&mdp->origin, ede.mld)) {
+ /* Race: connection down => do nothing */
+ erts_monitor_release_both(mdp);
+ break;
+ }
if (erts_proc_sig_send_monitor(&mdp->target, pid))
break; /* done */
@@ -2120,7 +2129,7 @@ int erts_net_message(Port *prt,
}
code = erts_dsig_prepare(&ctx, dep, NULL, 0, ERTS_DSP_NO_LOCK, 1, 1, 0);
- if (code == ERTS_DSIG_PREP_CONNECTED) {
+ if (code == ERTS_DSIG_PREP_CONNECTED && ctx.connection_id == conn_id) {
code = erts_dsig_send_m_exit(&ctx, watcher, watched, ref, am_noproc);
ASSERT(code == ERTS_DSIG_SEND_OK);
}
@@ -2156,16 +2165,17 @@ int erts_net_message(Port *prt,
;
}
else if (is_atom(watched)) {
- ErtsMonLnkDist *mld = dep->mld;
ErtsMonitor *mon;
- erts_mtx_lock(&mld->mtx);
-
- mon = erts_monitor_tree_lookup(mld->orig_name_monitors, ref);
- if (mon)
- erts_monitor_tree_delete(&mld->orig_name_monitors, mon);
-
- erts_mtx_unlock(&mld->mtx);
+ erts_mtx_lock(&ede.mld->mtx);
+ if (ede.mld->alive) {
+ mon = erts_monitor_tree_lookup(ede.mld->orig_name_monitors, ref);
+ if (mon)
+ erts_monitor_tree_delete(&ede.mld->orig_name_monitors, mon);
+ }
+ else
+ mon = NULL;
+ erts_mtx_unlock(&ede.mld->mtx);
if (mon)
erts_proc_sig_send_demonitor(mon);
@@ -2554,7 +2564,8 @@ int erts_net_message(Port *prt,
}
code = erts_dsig_prepare(&ctx, dep, NULL, 0,
ERTS_DSP_NO_LOCK, 1, 1, 0);
- if (code == ERTS_DSIG_PREP_CONNECTED) {
+ if (code == ERTS_DSIG_PREP_CONNECTED
+ && ctx.connection_id == conn_id) {
code = erts_dsig_send_spawn_reply(&ctx,
tuple[2],
tuple[3],
@@ -2572,6 +2583,7 @@ int erts_net_message(Port *prt,
so.group_leader = gl;
so.mfa = mfa;
so.dist_entry = dep;
+ so.mld = ede.mld;
so.edep = edep;
so.ede_hfrag = ede_hfrag;
so.token = token;
@@ -2598,6 +2610,7 @@ int erts_net_message(Port *prt,
ErtsLinkData *ldp;
ErtsLink *lnk;
int monitor;
+ int link_inserted;
Eterm ref, result, flags_term, parent, token;
Uint flags;
@@ -2619,6 +2632,7 @@ int erts_net_message(Port *prt,
ldp = NULL;
lnk = NULL;
+ link_inserted = 0;
monitor = 0;
ref = tuple[2];
@@ -2660,15 +2674,11 @@ int erts_net_message(Port *prt,
if (flags & ERTS_DIST_SPAWN_FLAG_LINK) {
/* Successful spawn-link... */
- int code;
-
ldp = erts_link_create(ERTS_LNK_TYPE_DIST_PROC,
result, parent);
ASSERT(ldp->a.other.item == parent);
ASSERT(eq(ldp->b.other.item, result));
- code = erts_link_dist_insert(&ldp->a, dep->mld);
- ASSERT(code); (void)code;
-
+ link_inserted = erts_link_dist_insert(&ldp->a, ede.mld);
lnk = &ldp->b;
}
}
@@ -2682,7 +2692,8 @@ int erts_net_message(Port *prt,
if (monitor) {
code = erts_dsig_prepare(&ctx, dep, NULL, 0,
ERTS_DSP_NO_LOCK, 1, 1, 0);
- if (code == ERTS_DSIG_PREP_CONNECTED) {
+ if (code == ERTS_DSIG_PREP_CONNECTED
+ && ctx.connection_id == conn_id) {
code = erts_dsig_send_demonitor(&ctx, parent,
result, ref);
ASSERT(code == ERTS_DSIG_SEND_OK);
@@ -2690,9 +2701,10 @@ int erts_net_message(Port *prt,
}
if (lnk) {
-
- code = erts_link_dist_delete(&ldp->a);
- ASSERT(code);
+ if (link_inserted) {
+ code = erts_link_dist_delete(&ldp->a);
+ ASSERT(code);
+ }
erts_link_release_both(ldp);
}
@@ -2706,6 +2718,10 @@ int erts_net_message(Port *prt,
dep->mld);
}
}
+ else if (lnk && !link_inserted) {
+ erts_proc_sig_send_link_exit(NULL, THE_NON_VALUE, &ldp->a,
+ am_noconnection, NIL);
+ }
break;
}
diff --git a/erts/emulator/beam/erl_alloc.types b/erts/emulator/beam/erl_alloc.types
index 48afa71583..7adab91440 100644
--- a/erts/emulator/beam/erl_alloc.types
+++ b/erts/emulator/beam/erl_alloc.types
@@ -354,18 +354,18 @@ type COUNTERS STANDARD SYSTEM erl_bif_counters
# Types used by system specific code
#
-type TEMP_TERM TEMPORARY SYSTEM temp_term
-type SHORT_LIVED_TERM SHORT_LIVED SYSTEM short_lived_term
-type DRV_TAB LONG_LIVED SYSTEM drv_tab
-type DRV_EV_STATE LONG_LIVED SYSTEM driver_event_state
-type DRV_SEL_D_STATE FIXED_SIZE SYSTEM driver_select_data_state
-type NIF_SEL_D_STATE FIXED_SIZE SYSTEM enif_select_data_state
-type POLLSET LONG_LIVED SYSTEM pollset
-type POLLSET_UPDREQ SHORT_LIVED SYSTEM pollset_update_req
-type POLL_FDS LONG_LIVED SYSTEM poll_fds
+type TEMP_TERM TEMPORARY SYSTEM temp_term
+type SHORT_LIVED_TERM SHORT_LIVED SYSTEM short_lived_term
+type DRV_TAB LONG_LIVED SYSTEM drv_tab
+type DRV_EV_STATE LONG_LIVED SYSTEM driver_event_state
+type DRV_SEL_D_STATE FIXED_SIZE SYSTEM driver_select_data_state
+type NIF_SEL_D_STATE FIXED_SIZE SYSTEM enif_select_data_state
+type POLLSET LONG_LIVED SYSTEM pollset
+type POLLSET_UPDREQ SHORT_LIVED SYSTEM pollset_update_req
+type POLL_FDS LONG_LIVED SYSTEM poll_fds
type BLOCK_PTHR_DATA LONG_LIVED SYSTEM block_poll_thread_data
-type FD_STATUS LONG_LIVED SYSTEM fd_status
-type SELECT_FDS LONG_LIVED SYSTEM select_fds
+type FD_STATUS LONG_LIVED SYSTEM fd_status
+type SELECT_FDS LONG_LIVED SYSTEM select_fds
+if unix
diff --git a/erts/emulator/beam/erl_map.c b/erts/emulator/beam/erl_map.c
index aac1ff7bfd..e7c649c5a7 100644
--- a/erts/emulator/beam/erl_map.c
+++ b/erts/emulator/beam/erl_map.c
@@ -2071,10 +2071,7 @@ Eterm erts_hashmap_insert(Process *p, Uint32 hx, Eterm key, Eterm value,
hp = HAlloc(p, size);
res = erts_hashmap_insert_up(hp, key, value, &upsz, &stack);
}
-
DESTROY_ESTACK(stack);
- ERTS_VERIFY_UNUSED_TEMP_ALLOC(p);
- ERTS_HOLE_CHECK(p);
return res;
}
@@ -2612,8 +2609,6 @@ unroll:
HRelease(p, hp_end, hp);
not_found:
DESTROY_ESTACK(stack);
- ERTS_VERIFY_UNUSED_TEMP_ALLOC(p);
- ERTS_HOLE_CHECK(p);
UnUseTmpHeapNoproc(2);
return res;
}
diff --git a/erts/emulator/beam/erl_monitor_link.c b/erts/emulator/beam/erl_monitor_link.c
index 432e895116..a1c5170386 100644
--- a/erts/emulator/beam/erl_monitor_link.c
+++ b/erts/emulator/beam/erl_monitor_link.c
@@ -544,9 +544,10 @@ erts_mon_link_dist_create(Eterm nodename)
return mld;
}
-void
-erts_mon_link_dist_destroy__(ErtsMonLnkDist *mld)
+static void
+mon_link_dist_destroy(void* vmld)
{
+ ErtsMonLnkDist *mld = (ErtsMonLnkDist*)vmld;
ERTS_ML_ASSERT(erts_atomic_read_nob(&mld->refc) == 0);
ERTS_ML_ASSERT(!mld->alive);
ERTS_ML_ASSERT(!mld->links);
@@ -558,6 +559,21 @@ erts_mon_link_dist_destroy__(ErtsMonLnkDist *mld)
erts_free(ERTS_ALC_T_ML_DIST, mld);
}
+void
+erts_schedule_mon_link_dist_destruction__(ErtsMonLnkDist *mld)
+{
+ ERTS_ML_ASSERT(erts_atomic_read_nob(&mld->refc) == 0);
+ ERTS_ML_ASSERT(!mld->alive);
+ ERTS_ML_ASSERT(!mld->links);
+ ERTS_ML_ASSERT(!mld->monitors);
+ ERTS_ML_ASSERT(!mld->orig_name_monitors);
+
+ erts_schedule_thr_prgr_later_cleanup_op(mon_link_dist_destroy,
+ mld,
+ &mld->cleanup_lop,
+ sizeof(ErtsMonLnkDist));
+}
+
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* Monitor Operations *
\* */
diff --git a/erts/emulator/beam/erl_monitor_link.h b/erts/emulator/beam/erl_monitor_link.h
index 15c0676988..d75bc7999b 100644
--- a/erts/emulator/beam/erl_monitor_link.h
+++ b/erts/emulator/beam/erl_monitor_link.h
@@ -396,6 +396,11 @@
#include "erl_proc_sig_queue.h"
#undef ERTS_PROC_SIG_QUEUE_TYPE_ONLY
+#define ERL_THR_PROGRESS_TSD_TYPE_ONLY
+#include "erl_thr_progress.h"
+#undef ERL_THR_PROGRESS_TSD_TYPE_ONLY
+
+
#if defined(DEBUG) || 0
# define ERTS_ML_DEBUG
#else
@@ -481,7 +486,7 @@ struct ErtsMonLnkNode__ {
Uint16 type;
};
-typedef struct {
+typedef struct ErtsMonLnkDist__ {
Eterm nodename;
Uint32 connection_id;
erts_atomic_t refc;
@@ -492,6 +497,7 @@ typedef struct {
ErtsMonLnkNode *orig_name_monitors; /* Origin named monitors
read-black tree */
ErtsMonLnkNode *dist_pend_spawn_exit;
+ ErtsThrPrgrLaterOp cleanup_lop;
} ErtsMonLnkDist;
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
@@ -544,7 +550,7 @@ ERTS_GLB_INLINE void erts_ml_dl_list_delete__(ErtsMonLnkNode **list,
ErtsMonLnkNode *ml);
ERTS_GLB_INLINE ErtsMonLnkNode *erts_ml_dl_list_first__(ErtsMonLnkNode *list);
ERTS_GLB_INLINE ErtsMonLnkNode *erts_ml_dl_list_last__(ErtsMonLnkNode *list);
-void erts_mon_link_dist_destroy__(ErtsMonLnkDist *mld);
+void erts_schedule_mon_link_dist_destruction__(ErtsMonLnkDist *mld);
ERTS_GLB_INLINE void *erts_ml_node_to_main_struct__(ErtsMonLnkNode *mln);
/* implementations for globally inlined misc functions... */
@@ -562,7 +568,7 @@ erts_mon_link_dist_dec_refc(ErtsMonLnkDist *mld)
{
ERTS_ML_ASSERT(erts_atomic_read_nob(&mld->refc) > 0);
if (erts_atomic_dec_read_nob(&mld->refc) == 0)
- erts_mon_link_dist_destroy__(mld);
+ erts_schedule_mon_link_dist_destruction__(mld);
}
ERTS_GLB_INLINE void *
@@ -1448,14 +1454,14 @@ erts_monitor_dist_insert(ErtsMonitor *mon, ErtsMonLnkDist *dist)
ERTS_ML_ASSERT(!mdep->dist);
ERTS_ML_ASSERT(dist);
- mdep->dist = dist;
-
- erts_mon_link_dist_inc_refc(dist);
erts_mtx_lock(&dist->mtx);
insert = dist->alive;
if (insert) {
+ mdep->dist = dist;
+ erts_mon_link_dist_inc_refc(dist);
+
if ((mon->flags & (ERTS_ML_FLG_NAME
| ERTS_ML_FLG_TARGET)) == ERTS_ML_FLG_NAME)
erts_monitor_tree_insert(&dist->orig_name_monitors, mon);
@@ -2346,15 +2352,15 @@ erts_link_dist_insert(ErtsLink *lnk, ErtsMonLnkDist *dist)
ERTS_ML_ASSERT(!ldep->dist);
ERTS_ML_ASSERT(dist);
- ldep->dist = dist;
-
- erts_mon_link_dist_inc_refc(dist);
erts_mtx_lock(&dist->mtx);
insert = dist->alive;
- if (insert)
+ if (insert) {
+ ldep->dist = dist;
+ erts_mon_link_dist_inc_refc(dist);
erts_link_list_insert(&dist->links, lnk);
+ }
erts_mtx_unlock(&dist->mtx);
diff --git a/erts/emulator/beam/erl_nif.c b/erts/emulator/beam/erl_nif.c
index 83bed11164..3f03243747 100644
--- a/erts/emulator/beam/erl_nif.c
+++ b/erts/emulator/beam/erl_nif.c
@@ -4605,6 +4605,7 @@ Eterm erts_load_nif(Process *c_p, BeamInstr *I, Eterm filename, Eterm args)
static void patch_call_nif_early(ErlNifEntry* entry,
struct erl_module_instance* this_mi)
{
+ const BeamInstr call_nif_early = BeamOpCodeAddr(op_call_nif_early);
int i;
ERTS_LC_ASSERT(erts_has_code_write_permission());
@@ -4627,13 +4628,13 @@ static void patch_call_nif_early(ErlNifEntry* entry,
* Code write permission protects against racing breakpoint writes.
*/
GenericBp* g = ci->u.gen_bp;
- g->orig_instr = BeamOpCodeAddr(op_call_nif_early);
+ g->orig_instr = BeamSetCodeAddr(g->orig_instr, call_nif_early);
if (BeamIsOpCode(code_ptr[0], op_i_generic_breakpoint))
continue;
}
else
ASSERT(!BeamIsOpCode(code_ptr[0], op_i_generic_breakpoint));
- code_ptr[0] = BeamOpCodeAddr(op_call_nif_early);
+ code_ptr[0] = BeamSetCodeAddr(code_ptr[0], call_nif_early);
}
}
@@ -4712,12 +4713,12 @@ static void load_nif_2nd_finisher(void* vlib)
* Function traced, patch the original instruction word
*/
GenericBp* g = ci->u.gen_bp;
- ASSERT(g->orig_instr == BeamOpCodeAddr(op_call_nif_early));
+ ASSERT(BeamIsOpCode(g->orig_instr, op_call_nif_early));
g->orig_instr = BeamOpCodeAddr(op_call_nif_WWW);
if (BeamIsOpCode(code_ptr[0], op_i_generic_breakpoint))
continue;
}
- ASSERT(code_ptr[0] == BeamOpCodeAddr(op_call_nif_early));
+ ASSERT(BeamIsOpCode(code_ptr[0], op_call_nif_early));
code_ptr[0] = BeamOpCodeAddr(op_call_nif_WWW);
}
}
diff --git a/erts/emulator/beam/erl_process.c b/erts/emulator/beam/erl_process.c
index ea3b369be2..3c8b1631b4 100644
--- a/erts/emulator/beam/erl_process.c
+++ b/erts/emulator/beam/erl_process.c
@@ -1634,6 +1634,11 @@ unset_aux_work_flags(ErtsSchedulerSleepInfo *ssi, erts_aint32_t flgs)
return erts_atomic32_read_band_nob(&ssi->aux_work, ~flgs);
}
+static ERTS_INLINE erts_aint32_t
+unset_aux_work_flags_mb(ErtsSchedulerSleepInfo *ssi, erts_aint32_t flgs)
+{
+ return erts_atomic32_read_band_mb(&ssi->aux_work, ~flgs);
+}
static ERTS_INLINE void
haw_chk_later_cleanup_op_wakeup(ErtsAuxWorkData *awdp, ErtsThrPrgrVal val)
@@ -1729,9 +1734,7 @@ handle_delayed_aux_work_wakeup(ErtsAuxWorkData *awdp, erts_aint32_t aux_work, in
if (!waiting && awdp->delayed_wakeup.next > awdp->esdp->reductions)
return aux_work;
- unset_aux_work_flags(awdp->ssi, ERTS_SSI_AUX_WORK_DELAYED_AW_WAKEUP);
-
- ERTS_THR_MEMORY_BARRIER;
+ unset_aux_work_flags_mb(awdp->ssi, ERTS_SSI_AUX_WORK_DELAYED_AW_WAKEUP);
max_jix = awdp->delayed_wakeup.jix;
awdp->delayed_wakeup.jix = -1;
@@ -1855,7 +1858,7 @@ handle_misc_aux_work(ErtsAuxWorkData *awdp,
{
ErtsThrQ_t *q = &misc_aux_work_queues[awdp->sched_id].q;
- unset_aux_work_flags(awdp->ssi, ERTS_SSI_AUX_WORK_MISC);
+ unset_aux_work_flags_mb(awdp->ssi, ERTS_SSI_AUX_WORK_MISC);
while (1) {
erts_misc_aux_work_t *mawp = erts_thr_q_dequeue(q);
if (!mawp)
@@ -1957,7 +1960,7 @@ handle_async_ready(ErtsAuxWorkData *awdp,
ASSERT(!awdp->esdp || !ERTS_SCHEDULER_IS_DIRTY(awdp->esdp));
- unset_aux_work_flags(ssi, ERTS_SSI_AUX_WORK_ASYNC_READY);
+ unset_aux_work_flags_mb(ssi, ERTS_SSI_AUX_WORK_ASYNC_READY);
if (erts_check_async_ready(awdp->async_ready.queue)) {
if (set_aux_work_flags(ssi, ERTS_SSI_AUX_WORK_ASYNC_READY)
& ERTS_SSI_AUX_WORK_ASYNC_READY_CLEAN) {
@@ -2013,8 +2016,8 @@ handle_fix_alloc(ErtsAuxWorkData *awdp, erts_aint32_t aux_work, int waiting)
ASSERT(!awdp->esdp || !ERTS_SCHEDULER_IS_DIRTY(awdp->esdp));
- unset_aux_work_flags(ssi, (ERTS_SSI_AUX_WORK_FIX_ALLOC_LOWER_LIM
- | ERTS_SSI_AUX_WORK_FIX_ALLOC_DEALLOC));
+ unset_aux_work_flags_mb(ssi, (ERTS_SSI_AUX_WORK_FIX_ALLOC_LOWER_LIM
+ | ERTS_SSI_AUX_WORK_FIX_ALLOC_DEALLOC));
aux_work &= ~(ERTS_SSI_AUX_WORK_FIX_ALLOC_LOWER_LIM
| ERTS_SSI_AUX_WORK_FIX_ALLOC_DEALLOC);
res = erts_alloc_fix_alloc_shrink(awdp->sched_id, aux_work);
@@ -2062,7 +2065,7 @@ handle_delayed_dealloc(ErtsAuxWorkData *awdp, erts_aint32_t aux_work, int waitin
ASSERT(!awdp->esdp || !ERTS_SCHEDULER_IS_DIRTY(awdp->esdp));
- unset_aux_work_flags(ssi, ERTS_SSI_AUX_WORK_DD);
+ unset_aux_work_flags_mb(ssi, ERTS_SSI_AUX_WORK_DD);
ERTS_MSACC_SET_STATE_CACHED_M_X(ERTS_MSACC_STATE_ALLOC);
erts_alloc_scheduler_handle_delayed_dealloc((void *) awdp->esdp,
&need_thr_progress,
@@ -2158,7 +2161,7 @@ handle_canceled_timers(ErtsAuxWorkData *awdp, erts_aint32_t aux_work, int waitin
ASSERT(!awdp->esdp || !ERTS_SCHEDULER_IS_DIRTY(awdp->esdp));
- unset_aux_work_flags(ssi, ERTS_SSI_AUX_WORK_CNCLD_TMRS);
+ unset_aux_work_flags_mb(ssi, ERTS_SSI_AUX_WORK_CNCLD_TMRS);
erts_handle_canceled_timers((void *) awdp->esdp,
&need_thr_progress,
&wakeup,
@@ -2328,7 +2331,7 @@ handle_debug_wait_completed(ErtsAuxWorkData *awdp, erts_aint32_t aux_work, int w
awdp->debug.wait_completed.callback = NULL;
awdp->debug.wait_completed.arg = NULL;
- unset_aux_work_flags(ssi, ERTS_SSI_AUX_WORK_DEBUG_WAIT_COMPLETED);
+ unset_aux_work_flags_mb(ssi, ERTS_SSI_AUX_WORK_DEBUG_WAIT_COMPLETED);
return aux_work & ~ERTS_SSI_AUX_WORK_DEBUG_WAIT_COMPLETED;
}
@@ -2464,7 +2467,7 @@ int erts_halt_code;
static ERTS_INLINE erts_aint32_t
handle_reap_ports(ErtsAuxWorkData *awdp, erts_aint32_t aux_work, int waiting)
{
- unset_aux_work_flags(awdp->ssi, ERTS_SSI_AUX_WORK_REAP_PORTS);
+ unset_aux_work_flags_mb(awdp->ssi, ERTS_SSI_AUX_WORK_REAP_PORTS);
ERTS_RUNQ_FLGS_SET(awdp->esdp->run_queue, ERTS_RUNQ_FLG_HALTING);
if (erts_atomic32_dec_read_acqb(&erts_halt_progress) == 0) {
@@ -2559,7 +2562,7 @@ handle_yield(ErtsAuxWorkData *awdp, erts_aint32_t aux_work, int waiting)
static ERTS_INLINE erts_aint32_t
handle_mseg_cache_check(ErtsAuxWorkData *awdp, erts_aint32_t aux_work, int waiting)
{
- unset_aux_work_flags(awdp->ssi, ERTS_SSI_AUX_WORK_MSEG_CACHE_CHECK);
+ unset_aux_work_flags_mb(awdp->ssi, ERTS_SSI_AUX_WORK_MSEG_CACHE_CHECK);
erts_mseg_cache_check();
return aux_work & ~ERTS_SSI_AUX_WORK_MSEG_CACHE_CHECK;
}
@@ -2570,7 +2573,7 @@ handle_mseg_cache_check(ErtsAuxWorkData *awdp, erts_aint32_t aux_work, int waiti
static ERTS_INLINE erts_aint32_t
handle_setup_aux_work_timer(ErtsAuxWorkData *awdp, erts_aint32_t aux_work, int waiting)
{
- unset_aux_work_flags(awdp->ssi, ERTS_SSI_AUX_WORK_SET_TMO);
+ unset_aux_work_flags_mb(awdp->ssi, ERTS_SSI_AUX_WORK_SET_TMO);
setup_aux_work_timer(awdp->esdp);
return aux_work & ~ERTS_SSI_AUX_WORK_SET_TMO;
}
@@ -8699,6 +8702,9 @@ erts_internal_suspend_process_2(BIF_ALIST_2)
if (BIF_P->common.id == BIF_ARG_1)
BIF_RET(am_badarg); /* We are not allowed to suspend ourselves */
+ if (!is_internal_pid(BIF_ARG_1))
+ BIF_RET(am_badarg);
+
if (is_not_nil(BIF_ARG_2)) {
/* Parse option list */
Eterm arg = BIF_ARG_2;
@@ -8847,6 +8853,9 @@ resume_process_1(BIF_ALIST_1)
if (BIF_P->common.id == BIF_ARG_1)
BIF_ERROR(BIF_P, BADARG);
+ if (!is_internal_pid(BIF_ARG_1))
+ BIF_ERROR(BIF_P, BADARG);
+
mon = erts_monitor_tree_lookup(ERTS_P_MONITORS(BIF_P),
BIF_ARG_1);
if (!mon) {
@@ -12209,9 +12218,11 @@ erl_create_process(Process* parent, /* Parent of process (default group leader).
ErtsLinkData *ldp;
ldp = erts_link_create(ERTS_LNK_TYPE_DIST_PROC,
parent_id, p->common.id);
- code = erts_link_dist_insert(&ldp->a, so->dist_entry->mld);
- ASSERT(code);
erts_link_tree_insert(&ERTS_P_LINKS(p), &ldp->b);
+ if (!erts_link_dist_insert(&ldp->a, so->mld)) {
+ erts_proc_sig_send_link_exit(NULL, THE_NON_VALUE, &ldp->a,
+ am_noconnection, NIL);
+ }
}
if (so->flags & SPO_MONITOR) {
@@ -12219,9 +12230,12 @@ erl_create_process(Process* parent, /* Parent of process (default group leader).
mdp = erts_monitor_create(ERTS_MON_TYPE_DIST_PROC,
spawn_ref, parent_id,
p->common.id, NIL);
- code = erts_monitor_dist_insert(&mdp->origin, so->dist_entry->mld);
- ASSERT(code); (void)code;
- erts_monitor_tree_insert(&ERTS_P_MONITORS(p), &mdp->target);
+ if (erts_monitor_dist_insert(&mdp->origin, so->mld)) {
+ erts_monitor_tree_insert(&ERTS_P_MONITORS(p), &mdp->target);
+ }
+ else {
+ erts_monitor_release_both(mdp);
+ }
}
if (have_seqtrace(token)) {
diff --git a/erts/emulator/beam/erl_process.h b/erts/emulator/beam/erl_process.h
index 8dfa5b1bbb..d89a00714f 100644
--- a/erts/emulator/beam/erl_process.h
+++ b/erts/emulator/beam/erl_process.h
@@ -1387,6 +1387,7 @@ typedef struct {
Eterm group_leader;
Eterm mfa;
DistEntry *dist_entry;
+ ErtsMonLnkDist *mld; /* copied from dist_entry->mld */
ErtsDistExternal *edep;
ErlHeapFragment *ede_hfrag;
Eterm token;
diff --git a/erts/emulator/beam/erl_process_dict.c b/erts/emulator/beam/erl_process_dict.c
index 64ee483079..248da52db5 100644
--- a/erts/emulator/beam/erl_process_dict.c
+++ b/erts/emulator/beam/erl_process_dict.c
@@ -1052,7 +1052,7 @@ static unsigned int next_array_size(unsigned int need)
1342177280UL,
2684354560UL
};
- int hi = sizeof(tab) / sizeof(Uint) - 1;
+ int hi = sizeof(tab) / sizeof(tab[0]) - 1;
int lo = 1;
int cur = 4;
diff --git a/erts/emulator/beam/external.c b/erts/emulator/beam/external.c
index 1a29904f5e..72edff0c22 100644
--- a/erts/emulator/beam/external.c
+++ b/erts/emulator/beam/external.c
@@ -883,6 +883,7 @@ erts_prepare_dist_ext(ErtsDistExternal *edep,
edep->heap_size = -1;
edep->flags = 0;
edep->dep = dep;
+ edep->mld = dep->mld;
edep->connection_id = conn_id;
edep->data->ext_endp = ext+size;
edep->data->binp = binp;
@@ -3838,7 +3839,7 @@ hopefull_bit_binary(TTBEncodeContext* ctx, byte **epp, Binary *pb_val, Eterm pb_
/* copy trailing bits into new hopefull data element */
ep = begin_hopefull_data(ctx, ep);
- *ep = 0;
+ *ep = 0; /* Clear the bit in the byte */
copy_binary_to_buffer(ep, 0, bytes + sz, bitoffs, bitsize);
ep++;
@@ -5241,10 +5242,13 @@ encode_size_struct_int(TTBSizeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj,
+ bin_size);
}
else if (dflags & DFLAG_PENDING_CONNECT) {
+ /* This is the odd case when we have an un-aligned bit-string
+ during a pending connect. */
Uint csz = result - ctx->last_result;
ASSERT(dflags & DFLAG_BIT_BINARIES);
/* potentially multiple elements leading up to binary */
- vlen += csz/MAX_SYSIOVEC_IOVLEN;
+ vlen += (csz + MAX_SYSIOVEC_IOVLEN - 1)/MAX_SYSIOVEC_IOVLEN;
+
vlen++; /* hopefull prolog */
/*
* Size for hopefull prolog is max of
diff --git a/erts/emulator/beam/external.h b/erts/emulator/beam/external.h
index 4d8f0bdd29..937dc532f6 100644
--- a/erts/emulator/beam/external.h
+++ b/erts/emulator/beam/external.h
@@ -144,6 +144,7 @@ typedef struct erl_dist_external {
Uint32 flags;
Uint32 connection_id;
ErtsDistExternalData *data;
+ struct ErtsMonLnkDist__ *mld; /* copied from DistEntry.mld */
ErtsAtomTranslationTable attab;
} ErtsDistExternal;
diff --git a/erts/emulator/beam/ops.tab b/erts/emulator/beam/ops.tab
index ed0b240d8a..14afdd8553 100644
--- a/erts/emulator/beam/ops.tab
+++ b/erts/emulator/beam/ops.tab
@@ -1356,6 +1356,9 @@ bs_append Fail Size Extra Live Unit Bin Flags Dst => \
bs_private_append Fail Size Unit Bin Flags Dst => \
i_bs_private_append Fail Unit Size Bin Dst
+i_bs_private_append Fail Unit Size Bin Dst=y => \
+ i_bs_private_append Fail Unit Size Bin x | move x Dst
+
bs_init_writable
i_bs_append j? I t? t s xy
diff --git a/erts/emulator/nifs/common/prim_file_nif.c b/erts/emulator/nifs/common/prim_file_nif.c
index fce4fc3c90..d26be2bc3e 100644
--- a/erts/emulator/nifs/common/prim_file_nif.c
+++ b/erts/emulator/nifs/common/prim_file_nif.c
@@ -1266,12 +1266,16 @@ static ERL_NIF_TERM read_file_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM a
if((posix_errno = efile_marshal_path(env, argv[0], &path))) {
return posix_error_to_tuple(env, posix_errno);
- } else if((posix_errno = efile_read_info(&path, 1, &info))) {
- return posix_error_to_tuple(env, posix_errno);
} else if((posix_errno = efile_open(&path, EFILE_MODE_READ, efile_resource_type, &d))) {
return posix_error_to_tuple(env, posix_errno);
}
+ /* read_file() wants to know the file size, so retrieve it now from the
+ open file handle. In theory, efile_read_handle_info() may fail with
+ ENOTSUP, fall back to the "unknown size" logic if that happens. */
+ if (efile_read_handle_info(d, &info) != 0) {
+ info.size = 0;
+ }
posix_errno = read_file(d, info.size, &result);
erts_atomic32_set_acqb(&d->state, EFILE_STATE_CLOSED);
diff --git a/erts/emulator/sys/common/erl_poll.c b/erts/emulator/sys/common/erl_poll.c
index 4e5918d1be..9f0c82dc4b 100644
--- a/erts/emulator/sys/common/erl_poll.c
+++ b/erts/emulator/sys/common/erl_poll.c
@@ -646,12 +646,13 @@ int erts_poll_new_table_len(int old_len, int need_len)
}
else {
new_len = old_len;
+ if (new_len < ERTS_FD_TABLE_MIN_LENGTH)
+ new_len = ERTS_FD_TABLE_MIN_LENGTH;
do {
if (new_len < ERTS_FD_TABLE_EXP_THRESHOLD)
new_len *= 2;
else
new_len += ERTS_FD_TABLE_EXP_THRESHOLD;
-
} while (new_len < need_len);
}
ASSERT(new_len >= need_len);
@@ -2062,6 +2063,21 @@ ERTS_POLL_EXPORT(erts_poll_init)(int *concurrent_updates)
max_fds = OPEN_MAX;
#endif
+ if (max_fds < 0 && errno == 0) {
+ /* On macOS 11 and higher, it possible to have an unlimited
+ * number of open files per process. ERTS will need an actual
+ * limit, though, so we will set it to a largish value. The
+ * number below is the hard number of file descriptors per
+ * process as returned by `sysctl kern.maxfilesperproc`, which
+ * seems to be the limit in practice.
+ *
+ * Note: The size of the port table will be based on max_fds,
+ * so we don't want to set it to a huge value such as
+ * MAX_INT.
+ */
+ max_fds = 24576;
+ }
+
#if ERTS_POLL_USE_SELECT && defined(FD_SETSIZE) && \
!defined(_DARWIN_UNLIMITED_SELECT)
if (max_fds > FD_SETSIZE)
diff --git a/erts/emulator/sys/win32/erl_win_sys.h b/erts/emulator/sys/win32/erl_win_sys.h
index b00ba287e2..4c64494ac0 100644
--- a/erts/emulator/sys/win32/erl_win_sys.h
+++ b/erts/emulator/sys/win32/erl_win_sys.h
@@ -52,10 +52,6 @@
#include <fcntl.h>
#include <time.h>
#include <sys/timeb.h>
-#pragma comment(linker,"/manifestdependency:\"type='win32' "\
- "name='Microsoft.Windows.Common-Controls' "\
- "version='6.0.0.0' processorArchitecture='*' "\
- "publicKeyToken='6595b64144ccf1df' language='*'\"")
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
diff --git a/erts/emulator/test/bs_bincomp_SUITE.erl b/erts/emulator/test/bs_bincomp_SUITE.erl
index c481e93e41..2dcebc2ef9 100644
--- a/erts/emulator/test/bs_bincomp_SUITE.erl
+++ b/erts/emulator/test/bs_bincomp_SUITE.erl
@@ -112,7 +112,14 @@ mixed(Config) when is_list(Config) ->
[(X+Y) || <<X:3>> <= <<1:3,2:3,3:3,4:3>>, <<Y:3>> <= <<1:3,2:3>>],
[2,3,3,4,4,5,5,6] =
[(X+Y) || <<X:3>> <= <<1:3,2:3,3:3,4:3>>, Y <- [1,2]],
- ok.
+
+ %% OTP-16899: Nested binary comprehensions would fail to load.
+ <<0,1,0,2,0,3,99>> = mixed_nested([1,2,3]),
+
+ ok.
+
+mixed_nested(L) ->
+ << << << << E:16 >> || E <- L >> || true >>/binary, 99:(id(8))>>.
%% OTP-8179: Call tracing on binary comprehensions would cause a crash.
tracing(Config) when is_list(Config) ->
@@ -148,3 +155,8 @@ tracer(Parent, N) ->
tracer(Parent, N+1)
end
end.
+
+%%% Common utilities.
+
+id(I) ->
+ I.
diff --git a/erts/emulator/test/distribution_SUITE.erl b/erts/emulator/test/distribution_SUITE.erl
index d945023f61..4982d979ec 100644
--- a/erts/emulator/test/distribution_SUITE.erl
+++ b/erts/emulator/test/distribution_SUITE.erl
@@ -75,7 +75,6 @@
system_limit/1,
hopefull_data_encoding/1,
hopefull_export_fun_bug/1,
- mk_hopefull_data/0,
huge_iovec/1]).
%% Internal exports.
@@ -2604,7 +2603,6 @@ test_hopefull_data_encoding(Config, Fallback) when is_list(Config) ->
false = rpc:call(BouncerNode, erts_debug, set_internal_state,
[remove_hopefull_dflags, true])
end,
- HData = mk_hopefull_data(),
Tester = self(),
R1 = make_ref(),
R2 = make_ref(),
@@ -2613,10 +2611,13 @@ test_hopefull_data_encoding(Config, Fallback) when is_list(Config) ->
Proxy = spawn_link(ProxyNode,
fun () ->
register(bouncer, self()),
+ %% We create the data on the proxy node in order
+ %% to create the correct sub binaries
+ HData = mk_hopefull_data(R1, Tester),
%% Verify same result between this node and tester
Tester ! [R1, HData],
%% Test when connection has not been setup yet
- Bouncer ! {Tester, [R2, HData]},
+ Bouncer ! {Tester, [R2, HData]},
Sync = make_ref(),
Bouncer ! {self(), Sync},
receive Sync -> ok end,
@@ -2624,17 +2625,18 @@ test_hopefull_data_encoding(Config, Fallback) when is_list(Config) ->
Bouncer ! {Tester, [R3, HData]},
receive after infinity -> ok end
end),
- receive
- [R1, HData1] ->
- Hdata = HData1
- end,
+ HData =
+ receive
+ [R1, HData1] ->
+ HData1
+ end,
receive
[R2, HData2] ->
case Fallback of
false ->
HData = HData2;
true ->
- check_hopefull_fallback_data(Hdata, HData2)
+ check_hopefull_fallback_data(HData, HData2)
end
end,
receive
@@ -2643,7 +2645,7 @@ test_hopefull_data_encoding(Config, Fallback) when is_list(Config) ->
false ->
HData = HData3;
true ->
- check_hopefull_fallback_data(Hdata, HData3)
+ check_hopefull_fallback_data(HData, HData3)
end
end,
unlink(Proxy),
@@ -2661,32 +2663,54 @@ bounce_loop() ->
end,
bounce_loop().
-mk_hopefull_data() ->
+mk_hopefull_data(RemoteRef, RemotePid) ->
HugeBs = list_to_bitstring([lists:duplicate(12*1024*1024, 85), <<6:6>>]),
<<_:1/bitstring,HugeBs2/bitstring>> = HugeBs,
- lists:flatten([mk_hopefull_data(list_to_binary(lists:seq(1,255))),
- 1234567890, HugeBs, fun gurka:banan/3, fun erlang:node/1,
- self(), fun erlang:self/0,
- mk_hopefull_data(list_to_binary(lists:seq(1,32))), an_atom,
- fun lists:reverse/1, make_ref(), HugeBs2,
- fun blipp:blapp/7]).
+ mk_hopefull_data(list_to_binary(lists:seq(1,255))) ++
+ [1234567890, HugeBs, fun gurka:banan/3, fun erlang:node/1,
+ RemotePid, self(), fun erlang:self/0] ++
+ mk_hopefull_data(list_to_binary(lists:seq(1,32))) ++
+ [an_atom,
+ fun lists:reverse/1, RemoteRef, make_ref(), HugeBs2,
+ fun blipp:blapp/7].
mk_hopefull_data(BS) ->
BSsz = bit_size(BS),
- [lists:map(fun (Offset) ->
- <<_:Offset/bitstring, NewBs/bitstring>> = BS,
- NewBs
- end, lists:seq(1, 16)),
- lists:map(fun (Offset) ->
- <<NewBs:Offset/bitstring, _/bitstring>> = BS,
- NewBs
- end, lists:seq(BSsz-16, BSsz-1)),
- lists:map(fun (Offset) ->
- PreOffset = Offset rem 16,
- <<_:PreOffset/bitstring, NewBs:Offset/bitstring, _/bitstring>> = BS,
- NewBs
- end, lists:seq(BSsz-32, BSsz-17))].
-
+ lists:concat(
+ [lists:map(fun (Offset) ->
+ <<NewBs:Offset/bitstring, _/bitstring>> = BS,
+ NewBs
+ end, lists:seq(1, 16)),
+ lists:map(fun (Offset) ->
+ <<_:Offset/bitstring, NewBs/bitstring>> = BS,
+ NewBs
+ end, lists:seq(1, 16)),
+ lists:map(fun (Offset) ->
+ <<NewBs:Offset/bitstring, _/bitstring>> = BS,
+ NewBs
+ end, lists:seq(BSsz-16, BSsz-1)),
+ lists:map(fun (Offset) ->
+ PreOffset = Offset rem 16,
+ <<_:PreOffset/bitstring, NewBs:Offset/bitstring, _/bitstring>> = BS,
+ NewBs
+ end, lists:seq(BSsz-32, BSsz-17)),
+ lists:map(fun (Offset) ->
+ <<NewBs:Offset/bitstring, _/bitstring>> = BS,
+ [NewBs]
+ end, lists:seq(1, 16)),
+ lists:map(fun (Offset) ->
+ <<_:Offset/bitstring, NewBs/bitstring>> = BS,
+ [NewBs]
+ end, lists:seq(1, 16)),
+ lists:map(fun (Offset) ->
+ <<NewBs:Offset/bitstring, _/bitstring>> = BS,
+ [NewBs]
+ end, lists:seq(BSsz-16, BSsz-1)),
+ lists:map(fun (Offset) ->
+ PreOffset = Offset rem 16,
+ <<_:PreOffset/bitstring, NewBs:Offset/bitstring, _/bitstring>> = BS,
+ [NewBs]
+ end, lists:seq(BSsz-32, BSsz-17))]).
check_hopefull_fallback_data([], []) ->
ok;
@@ -2696,6 +2720,8 @@ check_hopefull_fallback_data([X|Xs],[Y|Ys]) ->
chk_hopefull_fallback(Binary, FallbackBinary) when is_binary(Binary) ->
Binary = FallbackBinary;
+chk_hopefull_fallback([BitStr], [{Bin, BitSize}]) when is_bitstring(BitStr) ->
+ chk_hopefull_fallback(BitStr, {Bin, BitSize});
chk_hopefull_fallback(BitStr, {Bin, BitSize}) when is_bitstring(BitStr) ->
true = is_binary(Bin),
true = is_integer(BitSize),
diff --git a/erts/emulator/test/driver_SUITE.erl b/erts/emulator/test/driver_SUITE.erl
index 95cae93225..ddbade85c5 100644
--- a/erts/emulator/test/driver_SUITE.erl
+++ b/erts/emulator/test/driver_SUITE.erl
@@ -82,6 +82,8 @@
consume_timeslice/1,
env/1,
poll_pipe/1,
+ lots_of_used_fds_on_boot/1,
+ lots_of_used_fds_on_boot_slave/1,
z_test/1]).
-export([bin_prefix/2]).
@@ -159,7 +161,9 @@ groups() ->
[a_test, use_fallback_pollset,
bad_fd_in_pollset, fd_change,
steal_control, smp_select,
- driver_select_use, z_test]},
+ driver_select_use,
+ lots_of_used_fds_on_boot,
+ z_test]},
{ioq_exit, [],
[ioq_exit_ready_input, ioq_exit_ready_output,
ioq_exit_timeout, ioq_exit_ready_async,
@@ -1859,6 +1863,79 @@ driver_select_use0(Config) ->
ok = erl_ddll:stop(),
ok.
+lots_of_used_fds_on_boot(Config) ->
+ case os:type() of
+ {unix, _} -> lots_of_used_fds_on_boot_test(Config);
+ _ -> {skipped, "Unix only test"}
+ end.
+
+lots_of_used_fds_on_boot_test(Config) ->
+ %% Start a node in a wrapper which have lots of fds
+ %% open. This used to hang the whole VM at boot in
+ %% an eternal loop trying to figure out how to size
+ %% arrays in erts_poll() implementation.
+ Name = lots_of_used_fds_on_boot,
+ HostSuffix = lists:dropwhile(fun ($@) -> false; (_) -> true end,
+ atom_to_list(node())),
+ FullName = list_to_atom(atom_to_list(Name) ++ HostSuffix),
+ Pa = filename:dirname(code:which(?MODULE)),
+ Prog = case catch init:get_argument(progname) of
+ {ok,[[P]]} -> P;
+ _ -> exit(no_progname_argument_found)
+ end,
+ NameSw = case net_kernel:longnames() of
+ false -> "-sname ";
+ true -> "-name ";
+ _ -> exit(not_distributed_node)
+ end,
+ {ok, Pwd} = file:get_cwd(),
+ NameStr = atom_to_list(Name),
+ DataDir = proplists:get_value(data_dir, Config),
+ Wrapper = filename:join(DataDir, "lots_of_fds_used_wrapper"),
+ CmdLine = Wrapper ++ " " ++ Prog ++ " -noshell -noinput "
+ ++ NameSw ++ " " ++ NameStr ++ " "
+ ++ "-pa " ++ Pa ++ " "
+ ++ "-env ERL_CRASH_DUMP " ++ Pwd ++ "/erl_crash_dump." ++ NameStr ++ " "
+ ++ "-setcookie " ++ atom_to_list(erlang:get_cookie()) ++ " "
+ ++ "-s " ++ atom_to_list(?MODULE) ++ " lots_of_used_fds_on_boot_slave "
+ ++ atom_to_list(node()),
+ io:format("Starting node ~p: ~s~n", [FullName, CmdLine]),
+ net_kernel:monitor_nodes(true),
+ Port = case open_port({spawn, CmdLine}, [exit_status]) of
+ Prt when is_port(Prt) ->
+ Prt;
+ OPError ->
+ exit({failed_to_start_node, {open_port_error, OPError}})
+ end,
+ receive
+ {Port, {exit_status, 17}} ->
+ {skip, "Cannot open enough fds to test this"};
+ {Port, {exit_status, Error}} ->
+ exit({failed_to_start_node, {exit_status, Error}});
+ {nodeup, FullName} ->
+ io:format("~p connected!~n", [FullName]),
+ FullName = rpc:call(FullName, erlang, node, []),
+ rpc:cast(FullName, erlang, halt, []),
+ receive
+ {Port, {exit_status, 0}} ->
+ ok;
+ {Port, {exit_status, Error}} ->
+ exit({unexpected_exit_status, Error})
+ after 5000 ->
+ exit(missing_exit_status)
+ end
+ after 5000 ->
+ exit(connection_timeout)
+ end.
+
+lots_of_used_fds_on_boot_slave([Master]) ->
+ erlang:monitor_node(Master, true),
+ receive
+ {nodedown, Master} ->
+ erlang:halt()
+ end,
+ ok.
+
thread_mseg_alloc_cache_clean(Config) when is_list(Config) ->
case {erlang:system_info(threads),
erlang:system_info({allocator,mseg_alloc}),
diff --git a/erts/emulator/test/driver_SUITE_data/Makefile.src b/erts/emulator/test/driver_SUITE_data/Makefile.src
index bcabaa689d..77cbd34fb1 100644
--- a/erts/emulator/test/driver_SUITE_data/Makefile.src
+++ b/erts/emulator/test/driver_SUITE_data/Makefile.src
@@ -1,3 +1,7 @@
+CC = @CC@
+LD = @LD@
+CFLAGS = @CFLAGS@ @DEFS@
+CROSSLDFLAGS = @CROSSLDFLAGS@
MISC_DRVS = outputv_drv@dll@ \
timer_drv@dll@ \
@@ -30,7 +34,15 @@ VSN_MISMATCH_DRVS = zero_extended_marker_garb_drv@dll@ \
smaller_major_vsn_drv@dll@ \
smaller_minor_vsn_drv@dll@
-all: $(MISC_DRVS) $(SYS_INFO_DRVS) $(VSN_MISMATCH_DRVS)
+PROGS = lots_of_fds_used_wrapper@exe@
+
+all: $(MISC_DRVS) $(SYS_INFO_DRVS) $(VSN_MISMATCH_DRVS) $(PROGS)
+
+lots_of_fds_used_wrapper@exe@: lots_of_fds_used_wrapper@obj@
+ $(LD) $(CROSSLDFLAGS) -o lots_of_fds_used_wrapper lots_of_fds_used_wrapper@obj@ @LIBS@
+
+lots_of_fds_used_wrapper@obj@: lots_of_fds_used_wrapper.c
+ $(CC) -c -o lots_of_fds_used_wrapper@obj@ $(CFLAGS) lots_of_fds_used_wrapper.c
@SHLIB_RULES@
diff --git a/erts/emulator/test/driver_SUITE_data/lots_of_fds_used_wrapper.c b/erts/emulator/test/driver_SUITE_data/lots_of_fds_used_wrapper.c
new file mode 100644
index 0000000000..34d84827d5
--- /dev/null
+++ b/erts/emulator/test/driver_SUITE_data/lots_of_fds_used_wrapper.c
@@ -0,0 +1,61 @@
+#if !defined(__WIN32__)
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#endif
+
+int
+main(int argc, char *argv[])
+{
+#if !defined(__WIN32__)
+
+ char **exec_argv;
+ int fds[12000];
+ int max = sizeof(fds)/sizeof(fds[0]);
+ int i;
+
+ /* Open a bit more than 1024 file descriptors... */
+ for (i = 0; i < max; i++) {
+ fds[i] = open("/dev/null", 0, O_WRONLY);
+ if (fds[i] < 0) {
+ if (i < 1200)
+ return 17; /* Not enough fds for the test... */
+ max = i;
+ break;
+ }
+ }
+
+ /*
+ * Close some of the latest fds to give room for
+ * the emulators usage...
+ */
+ for (i = max-150; i < max; i++)
+ close(fds[i]);
+
+ if (argc < 2)
+ return 1;
+
+ /*
+ * Ensure NULL pointer after last argument...
+ */
+ exec_argv = malloc(sizeof(char *)*argc);
+ if (!exec_argv)
+ return 2;
+
+ for (i = 0; i < argc-1; i++) {
+ /* printf("arg=%d: %s\n", i, argv[i+1]); */
+ exec_argv[i] = argv[i+1];
+ }
+ exec_argv[i] = NULL;
+
+ execvp(exec_argv[0], exec_argv);
+
+ perror("Failed to exec");
+
+#endif
+
+ return 3;
+}
diff --git a/erts/emulator/test/driver_SUITE_data/peek_non_existing_queue_drv.c b/erts/emulator/test/driver_SUITE_data/peek_non_existing_queue_drv.c
index 685cda3e07..b69d75c31d 100644
--- a/erts/emulator/test/driver_SUITE_data/peek_non_existing_queue_drv.c
+++ b/erts/emulator/test/driver_SUITE_data/peek_non_existing_queue_drv.c
@@ -47,6 +47,10 @@
#include <windows.h>
#endif
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
#include <errno.h>
#include "erl_driver.h"
diff --git a/erts/emulator/test/driver_SUITE_data/thr_msg_blast_drv.c b/erts/emulator/test/driver_SUITE_data/thr_msg_blast_drv.c
index 56183c9484..503d8b902c 100644
--- a/erts/emulator/test/driver_SUITE_data/thr_msg_blast_drv.c
+++ b/erts/emulator/test/driver_SUITE_data/thr_msg_blast_drv.c
@@ -18,6 +18,10 @@
* %CopyrightEnd%
*/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
#include "erl_driver.h"
#define THR_MSG_BLAST_NO_PROCS 10
diff --git a/erts/emulator/test/estone_SUITE_data/estone_cat.c b/erts/emulator/test/estone_SUITE_data/estone_cat.c
index a34bda4384..cbdf3db6c9 100644
--- a/erts/emulator/test/estone_SUITE_data/estone_cat.c
+++ b/erts/emulator/test/estone_SUITE_data/estone_cat.c
@@ -12,9 +12,11 @@
#include <fcntl.h>
#include <errno.h>
-main(argc, argv)
-int argc;
-char *argv[];
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+int main(int argc, char* argv[])
{
char buf[16384];
int n;
diff --git a/erts/emulator/test/mtx_SUITE_data/mtx_SUITE.c b/erts/emulator/test/mtx_SUITE_data/mtx_SUITE.c
index 46ee8b5540..6f662ae514 100644
--- a/erts/emulator/test/mtx_SUITE_data/mtx_SUITE.c
+++ b/erts/emulator/test/mtx_SUITE_data/mtx_SUITE.c
@@ -39,6 +39,7 @@
#include <errno.h>
#include <stdio.h>
+#include <string.h>
static int
fail(const char *file, int line, const char *function, const char *assertion);
diff --git a/erts/emulator/test/nif_SUITE.erl b/erts/emulator/test/nif_SUITE.erl
index 273d9b1543..2cc3b8db32 100644
--- a/erts/emulator/test/nif_SUITE.erl
+++ b/erts/emulator/test/nif_SUITE.erl
@@ -1434,6 +1434,21 @@ maps(Config) when is_list(Config) ->
end,
{1,#{}}),
+ M5 = lists:foldl(fun(N, MapIn) ->
+ {1, #{N := value}=MapOut} = make_map_put_nif(MapIn, N, value),
+ MapOut
+ end,
+ #{},
+ lists:seq(1,40)),
+ M6 = lists:foldl(fun(N, MapIn) ->
+ {1, MapOut} = make_map_remove_nif(MapIn, N),
+ ok = maps:get(N, MapOut, ok),
+ MapOut
+ end,
+ M5,
+ lists:seq(1,40)),
+ true = (M6 =:= #{}),
+
has_duplicate_keys = maps_from_list_nif([{1,1},{1,1}]),
verify_tmpmem(TmpMem),
diff --git a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c
index a6ed6ae15f..93708fa99c 100644
--- a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c
+++ b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c
@@ -2123,6 +2123,19 @@ static ERL_NIF_TERM make_map_put_nif(ErlNifEnv* env, int argc, const ERL_NIF_TER
{
ERL_NIF_TERM map_out = enif_make_atom(env, "undefined");
int ret = enif_make_map_put(env, argv[0], argv[1], argv[2], &map_out);
+
+ /* build same map in dynamic env */
+ ErlNifEnv* dynenv = enif_alloc_env();
+ ERL_NIF_TERM map_out2 = enif_make_atom(env, "undefined");
+ int ret2 = enif_make_map_put(dynenv,
+ enif_make_copy(dynenv, argv[0]),
+ enif_make_copy(dynenv, argv[1]),
+ enif_make_copy(dynenv, argv[2]),
+ &map_out2);
+ if (ret != ret2 || !enif_is_identical(map_out, map_out2))
+ map_out = enif_make_string(env, "dynenv failure", ERL_NIF_LATIN1);
+ enif_free_env(dynenv);
+
return enif_make_tuple2(env, enif_make_int(env,ret), map_out);
}
static ERL_NIF_TERM get_map_value_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
@@ -2136,12 +2149,37 @@ static ERL_NIF_TERM make_map_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_
{
ERL_NIF_TERM map_out = enif_make_atom(env, "undefined");
int ret = enif_make_map_update(env, argv[0], argv[1], argv[2], &map_out);
+
+ /* build same map in dynamic env */
+ ErlNifEnv* dynenv = enif_alloc_env();
+ ERL_NIF_TERM map_out2 = enif_make_atom(env, "undefined");
+ int ret2 = enif_make_map_update(dynenv,
+ enif_make_copy(dynenv, argv[0]),
+ enif_make_copy(dynenv, argv[1]),
+ enif_make_copy(dynenv, argv[2]),
+ &map_out2);
+ if (ret != ret2 || !enif_is_identical(map_out, map_out2))
+ map_out = enif_make_string(env, "dynenv failure", ERL_NIF_LATIN1);
+ enif_free_env(dynenv);
+
return enif_make_tuple2(env, enif_make_int(env,ret), map_out);
}
static ERL_NIF_TERM make_map_remove_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
ERL_NIF_TERM map_out = enif_make_atom(env, "undefined");
int ret = enif_make_map_remove(env, argv[0], argv[1], &map_out);
+
+ /* build same map in dynamic env */
+ ErlNifEnv* dynenv = enif_alloc_env();
+ ERL_NIF_TERM map_out2 = enif_make_atom(env, "undefined");
+ int ret2 = enif_make_map_remove(dynenv,
+ enif_make_copy(dynenv, argv[0]),
+ enif_make_copy(dynenv, argv[1]),
+ &map_out2);
+ if (ret != ret2 || !enif_is_identical(map_out, map_out2))
+ map_out = enif_make_string(env, "dynenv failure", ERL_NIF_LATIN1);
+ enif_free_env(dynenv);
+
return enif_make_tuple2(env, enif_make_int(env,ret), map_out);
}
@@ -3345,15 +3383,15 @@ static ERL_NIF_TERM ioq(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
ret = enif_make_resource(env, ioq);
enif_release_resource(ioq);
return ret;
- } else if (enif_is_identical(argv[0], enif_make_atom(env, "inspect"))) {
+ } else if (argc >= 2 && enif_is_identical(argv[0], enif_make_atom(env, "inspect"))) {
ErlNifIOVec vec, *iovec = NULL;
int i, iovcnt;
ERL_NIF_TERM *elems, tail, list;
ErlNifEnv *myenv = NULL;
- if (enif_is_identical(argv[2], enif_make_atom(env, "use_stack")))
+ if (argv >= 3 && enif_is_identical(argv[2], enif_make_atom(env, "use_stack")))
iovec = &vec;
- if (enif_is_identical(argv[3], enif_make_atom(env, "use_env")))
+ if (argc >= 4 && enif_is_identical(argv[3], enif_make_atom(env, "use_env")))
myenv = env;
if (!enif_inspect_iovec(myenv, ~(size_t)0, argv[1], &tail, &iovec))
return enif_make_badarg(env);
@@ -3378,13 +3416,13 @@ static ERL_NIF_TERM ioq(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
list = enif_make_list_from_array(env, elems, iovcnt);
enif_free(elems);
return list;
- } else {
+ } else if (argc >= 2) {
unsigned skip;
if (!enif_get_resource(env, argv[1], ioq_resource_type, (void**)&ioq)
|| !ioq->q)
return enif_make_badarg(env);
- if (enif_is_identical(argv[0], enif_make_atom(env, "example"))) {
+ if (argc == 3 && enif_is_identical(argv[0], enif_make_atom(env, "example"))) {
#ifndef __WIN32__
int fd[2], res = 0, cnt = 0;
ERL_NIF_TERM tail;
@@ -3434,7 +3472,7 @@ static ERL_NIF_TERM ioq(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
enif_ioq_destroy(ioq->q);
ioq->q = NULL;
return enif_make_atom(env, "false");
- } else if (enif_is_identical(argv[0], enif_make_atom(env, "enqv"))) {
+ } else if (argc >= 4 && enif_is_identical(argv[0], enif_make_atom(env, "enqv"))) {
ErlNifIOVec vec, *iovec = &vec;
ERL_NIF_TERM tail;
@@ -3446,7 +3484,7 @@ static ERL_NIF_TERM ioq(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
return enif_make_badarg(env);
return enif_make_atom(env, "true");
- } else if (enif_is_identical(argv[0], enif_make_atom(env, "enqb"))) {
+ } else if (argc >= 4 && enif_is_identical(argv[0], enif_make_atom(env, "enqb"))) {
ErlNifBinary bin;
if (!enif_get_uint(env, argv[3], &skip) ||
!enif_inspect_binary(env, argv[2], &bin))
@@ -3456,7 +3494,7 @@ static ERL_NIF_TERM ioq(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
return enif_make_badarg(env);
return enif_make_atom(env, "true");
- } else if (enif_is_identical(argv[0], enif_make_atom(env, "enqbraw"))) {
+ } else if (argc >= 4 && enif_is_identical(argv[0], enif_make_atom(env, "enqbraw"))) {
ErlNifBinary bin;
ErlNifBinary localbin;
int i;
@@ -3480,7 +3518,7 @@ static ERL_NIF_TERM ioq(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
}
return enif_make_atom(env, "false");
- } else if (enif_is_identical(argv[0], enif_make_atom(env, "peek"))) {
+ } else if (argc >= 3 && enif_is_identical(argv[0], enif_make_atom(env, "peek"))) {
int iovlen, num, i, off = 0;
SysIOVec *iov = enif_ioq_peek(ioq->q, &iovlen);
ErlNifBinary bin;
@@ -3496,7 +3534,7 @@ static ERL_NIF_TERM ioq(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
}
return enif_make_binary(env, &bin);
- } else if (enif_is_identical(argv[0], enif_make_atom(env, "deq"))) {
+ } else if (argc >= 3 && enif_is_identical(argv[0], enif_make_atom(env, "deq"))) {
int num;
size_t sz;
ErlNifUInt64 sz64;
diff --git a/erts/emulator/test/num_bif_SUITE.erl b/erts/emulator/test/num_bif_SUITE.erl
index 6b834705cf..fded36431e 100644
--- a/erts/emulator/test/num_bif_SUITE.erl
+++ b/erts/emulator/test/num_bif_SUITE.erl
@@ -544,7 +544,7 @@ t_string_to_integer(Config) when is_list(Config) ->
test_sti(268435455),
test_sti(-268435455),
- % Interesting values around 2-pows, such as MIN_SMALL and MAX_SMALL.
+ %% Interesting values around 2-pows, such as MIN_SMALL and MAX_SMALL.
lists:foreach(fun(Bits) ->
N = 1 bsl Bits,
test_sti(N - 1),
@@ -553,11 +553,11 @@ t_string_to_integer(Config) when is_list(Config) ->
end,
lists:seq(16, 130)),
- %% Bignums.
+ %% Bignums
test_sti(123456932798748738738,16),
test_sti(list_to_integer(lists:duplicate(2000, $1))),
- %% unalign string
+ %% Unaligned string
Str = <<"10">>,
UnalignStr = <<0:3, (id(Str))/binary, 0:5>>,
<<_:3, SomeStr:2/binary, _:5>> = id(UnalignStr),
@@ -568,32 +568,39 @@ t_string_to_integer(Config) when is_list(Config) ->
{'EXIT', {badarg, _}} =
(catch binary_to_integer(Value)),
{'EXIT', {badarg, _}} =
- (catch erlang:list_to_integer(Value))
+ (catch list_to_integer(Value))
end,[atom,1.2,0.0,[$1,[$2]]]),
- % Default base error cases
+ %% Default base error cases
lists:foreach(fun(Value) ->
{'EXIT', {badarg, _}} =
- (catch erlang:binary_to_integer(
- list_to_binary(Value))),
+ (catch binary_to_integer(list_to_binary(Value))),
{'EXIT', {badarg, _}} =
- (catch erlang:list_to_integer(Value))
+ (catch list_to_integer(Value))
end,["1.0"," 1"," -1","","+"]),
- % Custom base error cases
+ %% Custom base error cases
lists:foreach(fun({Value,Base}) ->
{'EXIT', {badarg, _}} =
- (catch binary_to_integer(
- list_to_binary(Value),Base)),
+ (catch binary_to_integer(list_to_binary(Value), Base)),
{'EXIT', {badarg, _}} =
- (catch erlang:list_to_integer(Value,Base))
- end,[{" 1",1},{" 1",37},{"2",2},{"B",11},{"b",11},{":", 16},
- {"1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111z",16},
- {"1z111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111",16},
- {"111z11111111",16}]),
-
- %% log2 calculation overflow bug in do_integer_to_list (OTP-12624)
- %% Would crash with segv
+ (catch list_to_integer(Value, Base))
+ end,
+ [{" 1",1},{" 1",37},{"2",2},{"B",11},{"b",11},{":", 16},
+ {"1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111z",16},
+ {"1z111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111",16},
+ {"111z11111111",16},
+ %% Untagging atoms at the beginning of atom.names
+ %% would produce a base in the valid range.
+ {"10",true}, %Base 4
+ {"10",'_'}, %Base 8
+ {"10",nonode@nohost}, %Base 12
+ {"10",'$end_of_table'}, %Base 16
+ {"10",''} %Base 20
+ ]),
+
+ %% log2 calculation overflow bug in do_integer_to_list (OTP-12624).
+ %% Would crash with segementation fault.
0 = list_to_integer(lists:duplicate(10000000,$0)),
ok.
diff --git a/erts/emulator/test/port_bif_SUITE_data/port_test.c b/erts/emulator/test/port_bif_SUITE_data/port_test.c
index 923ab99ccc..ef6d12dc93 100644
--- a/erts/emulator/test/port_bif_SUITE_data/port_test.c
+++ b/erts/emulator/test/port_bif_SUITE_data/port_test.c
@@ -10,6 +10,7 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
+#include <ctype.h>
#ifndef __WIN32__
#include <unistd.h>
@@ -32,7 +33,7 @@
exit(1); \
}
-#define MAIN(argc, argv) main(argc, argv)
+#define MAIN(argc, argv) int main(argc, argv)
extern int errno;
diff --git a/erts/emulator/test/trace_SUITE_data/slow_drv.c b/erts/emulator/test/trace_SUITE_data/slow_drv.c
index 4f7c93a69e..e7c1eb2125 100644
--- a/erts/emulator/test/trace_SUITE_data/slow_drv.c
+++ b/erts/emulator/test/trace_SUITE_data/slow_drv.c
@@ -3,6 +3,12 @@
#endif
#include <stdio.h>
+#include <string.h>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
#include "erl_driver.h"
typedef struct _erl_drv_data {
diff --git a/erts/emulator/utils/make_driver_tab b/erts/emulator/utils/make_driver_tab
index a000b9d415..78b6fba254 100755
--- a/erts/emulator/utils/make_driver_tab
+++ b/erts/emulator/utils/make_driver_tab
@@ -52,6 +52,7 @@ while (@ARGV) {
if ( $d =~ /^.*\.a$/ ) {
$d = basename $d;
$d =~ s/\.a$//; # strip .a
+ $d =~ s/\.gprof$//; # strip .gprof
if ($mode == 1) {
push(@static_drivers, $d);
}
diff --git a/erts/emulator/valgrind/suppress.standard b/erts/emulator/valgrind/suppress.standard
index 3d6b4b50d3..a029ea7d37 100644
--- a/erts/emulator/valgrind/suppress.standard
+++ b/erts/emulator/valgrind/suppress.standard
@@ -277,7 +277,36 @@ obj:*/ssleay.*
Memcheck:Addr8
fun:RC4
}
-
+{
+Crypto internal... loading gives expected errors when curves are tried. But including <openssl/err.h> and removing them triggers compiler errors on Windows
+Memcheck:Leak
+fun:malloc
+...
+fun:valid_curve
+fun:init_curves
+fun:init_curve_types
+fun:init_algorithms_types
+fun:initialize
+fun:load
+fun:erts_load_nif
+fun:process_main
+fun:sched_thread_func
+}
+{
+Crypto internal.. loading pecularities revisited
+Memcheck:Leak
+fun:malloc
+fun:CRYPTO_malloc
+fun:lh_new
+...
+fun:ecdh_check
+fun:ECDH_compute_key
+fun:ecdh_compute_key_nif
+fun:process_main
+fun:sched_thread_func
+fun:thr_wrapper
+fun:start_thread
+}
{
Prebuilt constant terms in os_info_init (PossiblyLost)
Memcheck:Leak
diff --git a/erts/etc/common/erlexec.c b/erts/etc/common/erlexec.c
index 59dcf700d7..9dba684cbb 100644
--- a/erts/etc/common/erlexec.c
+++ b/erts/etc/common/erlexec.c
@@ -1199,10 +1199,10 @@ usage_aux(void)
#endif
"[-make] [-man [manopts] MANPAGE] [-x] [-emu_args] [-start_epmd BOOLEAN] "
"[-args_file FILENAME] [+A THREADS] [+a SIZE] [+B[c|d|i]] [+c [BOOLEAN]] "
- "[+C MODE] [+h HEAP_SIZE_OPTION] [+K BOOLEAN] "
- "[+l] [+M<SUBSWITCH> <ARGUMENT>] [+P MAX_PROCS] [+Q MAX_PORTS] "
+ "[+C MODE] [+dcg DECENTRALIZED_COUNTER_GROUPS_LIMIT] [+h HEAP_SIZE_OPTION] "
+ "[+M<SUBSWITCH> <ARGUMENT>] [+P MAX_PROCS] [+Q MAX_PORTS] "
"[+R COMPAT_REL] "
- "[+r] [+rg READER_GROUPS_LIMIT] [+s SCHEDULER_OPTION] "
+ "[+r] [+rg READER_GROUPS_LIMIT] [+s<SUBSWITCH> SCHEDULER_OPTION] "
"[+S NO_SCHEDULERS:NO_SCHEDULERS_ONLINE] "
"[+SP PERCENTAGE_SCHEDULERS:PERCENTAGE_SCHEDULERS_ONLINE] "
"[+T LEVEL] [+V] [+v] "
diff --git a/erts/etc/unix/etp-commands.in b/erts/etc/unix/etp-commands.in
index 9ed887c3d4..f440b6a882 100644
--- a/erts/etc/unix/etp-commands.in
+++ b/erts/etc/unix/etp-commands.in
@@ -646,6 +646,67 @@ define etp-atom-1
end
+define etp-string-to-atom
+# Args: (char*) null-terminated
+#
+# Non-reentrant
+
+ set $etp_i = 0
+ set $etp_h = ((UWord)0)
+ while (($arg0)[$etp_i]) != 0
+ set $etp_c = (unsigned char)(($arg0)[$etp_i])
+
+ if $etp_c & 0x80
+ printf "Non ASCII atoms not implemented\n"
+ loop_break
+ end
+
+ set $etp_h = ($etp_h << 4) + $etp_c
+ set $etp_g = $etp_h & 0xf0000000
+ if $etp_g != 0
+ set $etp_h ^= ($etp_g >> 24)
+ set $etp_h ^= $etp_g
+ end
+ set $etp_i++
+ end
+
+ # hash_get_slot
+ set $etp_h ^= $etp_h >> erts_atom_table.htable.shift
+ if $etp_arch64
+ set $etp_h = (11400714819323198485UL * $etp_h) >> erts_atom_table.htable.shift
+ else
+ set $etp_h = (2654435769UL * $etp_h) >> erts_atom_table.htable.shift
+ end
+ set $etp_p = (Atom*)erts_atom_table.htable.bucket[$etp_h]
+
+ # search hash bucket list
+ while $etp_p
+ set $etp_i = 0
+ while $etp_i < $etp_p->len && ($arg0)[$etp_i]
+ if $etp_p->name[$etp_i] != ($arg0)[$etp_i]
+ loop_break
+ end
+ set $etp_i++
+ end
+ if $etp_i == $etp_p->len && ($arg0)[$etp_i] == 0
+ loop_break
+ end
+ set $etp_p = (Atom*)$etp_p->slot.bucket.next
+ end
+ if $etp_p
+ print ($etp_p->slot.index << 6) | (2 << 2) | 3
+ else
+ printf "Can't find atom\n"
+ end
+end
+
+document etp-string-to-atom
+%----------------------------------------
+% etp-string-to-atom (char*)
+%
+% Ex: etp-string-to-atom "erlang"
+%----------------------------------------
+end
define etp-char-1
# Args: int char, int quote_char
@@ -1093,6 +1154,92 @@ document etp-mfa
%---------------------------------------------------------------------------
end
+define etp-export-get
+ # Args: Eterm Eterm Uint
+
+ set $etp_h = (((Eterm)$arg0 >> 6) * ((Eterm)$arg1 >> 6)) ^ (Uint)$arg2
+
+ #hash_get_slot
+ set $etp_t = &export_tables[the_active_code_index.counter].htable
+ set $etp_h ^= $etp_h >> $etp_t->shift
+ if $etp_arch64
+ set $etp_h = (11400714819323198485UL * $etp_h) >> $etp_t->shift
+ else
+ set $etp_h = (2654435769UL * $etp_h) >> $etp_t->shift
+ end
+
+ set $etp_p = (struct export_entry*) $etp_t->bucket[$etp_h]
+ while $etp_p
+ if $etp_p->ep->info.mfa.module == $arg0 && $etp_p->ep->info.mfa.function == $arg1 && $etp_p->ep->info.mfa.arity == $arg2
+ loop_break
+ end
+ set $etp_p = (struct export_entry*) $etp_p->slot.bucket.next
+ end
+ if $etp_p
+ print $etp_p->ep
+ else
+ printf "Can't find export entry\n"
+ end
+end
+
+document etp-export-get
+%---------------------------------------------------------
+% etp-export-get module function arity
+%
+% Lookup and print pointer to Export entry.
+% Example:
+% (gdb) etp-string-to-atom "erlang"
+% $1 = 13323
+% (gdb) etp-string-to-atom "self"
+% $2 = 47115
+% (gdb) etp-export-get 13323 47115 0
+% $3 = (Export *) 0x7f53caf1f358
+%---------------------------------------------------------
+end
+
+define etp-module-get
+ # Args: Eterm
+
+ set $etp_ix = ((Eterm)$arg0 >> 6)
+ set $etp_h = $etp_ix
+
+ #hash_get_slot
+ set $etp_t = &module_tables[the_active_code_index.counter].htable
+ set $etp_h ^= $etp_h >> $etp_t->shift
+ if $etp_arch64
+ set $etp_h = (11400714819323198485UL * $etp_h) >> $etp_t->shift
+ else
+ set $etp_h = (2654435769UL * $etp_h) >> $etp_t->shift
+ end
+
+ set $etp_p = (Module*) $etp_t->bucket[$etp_h]
+ while $etp_p
+ if $etp_p->module == $etp_ix
+ loop_break
+ end
+ set $etp_p = (Module*) $etp_p->slot.bucket.next
+ end
+ if $etp_p
+ print $etp_p
+ else
+ printf "Can't find module entry\n"
+ end
+end
+
+document etp-module-get
+%---------------------------------------------------------
+% etp-module-get module
+%
+% Lookup and print pointer to Module entry.
+% Example:
+% (gdb) etp-string-to-atom "erlang"
+% $1 = 13323
+% (gdb) etp-module-get 13323
+% $2 = (Module *) 0x7f53caf1f358
+%---------------------------------------------------------
+end
+
+
define etp-cp-func-info-1
# Args: Eterm cp
#
diff --git a/erts/etc/win32/erl.c b/erts/etc/win32/erl.c
index 7cbd0d027c..e960fb1238 100644
--- a/erts/etc/win32/erl.c
+++ b/erts/etc/win32/erl.c
@@ -17,10 +17,7 @@
*
* %CopyrightEnd%
*/
-#pragma comment(linker,"/manifestdependency:\"type='win32' "\
- "name='Microsoft.Windows.Common-Controls' "\
- "version='6.0.0.0' processorArchitecture='*' "\
- "publicKeyToken='6595b64144ccf1df' language='*'\"")
+
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
diff --git a/erts/etc/win32/manifest.xml b/erts/etc/win32/manifest.xml
new file mode 100644
index 0000000000..eea364c9e9
--- /dev/null
+++ b/erts/etc/win32/manifest.xml
@@ -0,0 +1,17 @@
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+ <dependency>
+ <dependentAssembly>
+ <assemblyIdentity
+ type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0"
+ processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*">
+ </assemblyIdentity>
+ </dependentAssembly>
+ </dependency>
+ <ms_asmv2:trustInfo xmlns:ms_asmv2="urn:schemas-microsoft-com:asm.v2">
+ <ms_asmv2:security>
+ <ms_asmv2:requestedPrivileges>
+ <ms_asmv2:requestedExecutionLevel level="AsInvoker" uiAccess="false"></ms_asmv2:requestedExecutionLevel>
+ </ms_asmv2:requestedPrivileges>
+ </ms_asmv2:security>
+ </ms_asmv2:trustInfo>
+</assembly>
diff --git a/erts/etc/win32/nsis/erlang20.nsi b/erts/etc/win32/nsis/erlang20.nsi
index 5a79101b5d..907a64b89c 100644
--- a/erts/etc/win32/nsis/erlang20.nsi
+++ b/erts/etc/win32/nsis/erlang20.nsi
@@ -144,7 +144,21 @@ SubSection /e "Erlang" SecErlang
Section "Development" SecErlangDev
SectionIn 1 RO
+
SetOutPath "$INSTDIR"
+
+; Don't let Users nor Autenticated Users group create new files
+; Avoid dll injection when installing to non /Program Files/ dirs
+
+ StrCmp $INSTDIR $InstallDir cp_files
+ ; Remove ANY inherited access control
+ ExecShellWait "open" "$SYSDIR\icacls.exe" '"$INSTDIR" /inheritance:r' SW_HIDE
+ ; Grant Admin full control
+ ExecShellWait "open" "$SYSDIR\icacls.exe" '"$INSTDIR" /grant:r *S-1-5-32-544:(OI)(CI)F' SW_HIDE
+ ; Grant Normal Users read+execute control
+ ExecShellWait "open" "$SYSDIR\icacls.exe" '"$INSTDIR" /grant:r *S-1-1-0:(OI)(CI)RX' SW_HIDE
+
+cp_files:
File "${TESTROOT}\Install.ini"
File "${TESTROOT}\Install.exe"
SetOutPath "$INSTDIR\releases"
diff --git a/erts/etc/win32/win_erlexec.c b/erts/etc/win32/win_erlexec.c
index c0bb92793e..defa654ad8 100644
--- a/erts/etc/win32/win_erlexec.c
+++ b/erts/etc/win32/win_erlexec.c
@@ -22,11 +22,6 @@
* Most of this only used when beam is run as a separate process.
*/
-#pragma comment(linker,"/manifestdependency:\"type='win32' "\
- "name='Microsoft.Windows.Common-Controls' "\
- "version='6.0.0.0' processorArchitecture='*' "\
- "publicKeyToken='6595b64144ccf1df' language='*'\"")
-
#include <windows.h>
#include <winuser.h>
#include <wincon.h>
diff --git a/erts/etc/win32/wsl_tools/vc/ld.sh b/erts/etc/win32/wsl_tools/vc/ld.sh
index fc115bec8c..a16c502cea 100755
--- a/erts/etc/win32/wsl_tools/vc/ld.sh
+++ b/erts/etc/win32/wsl_tools/vc/ld.sh
@@ -177,11 +177,16 @@ RES=$?
CMANIFEST=`w32_path.sh -u $MANIFEST`
-if [ "$RES" = "0" -a -f "$CMANIFEST" ]; then
- # Add stuff to manifest to turn off "virtualization"
+if [ -f "$CMANIFEST" ]; then
+ ## Add stuff to manifest to turn off "virtualization"
sed -n -i '1h;1!H;${;g;s,<trustInfo.*</trustInfo>.,,g;p;}' $CMANIFEST 2>/dev/null
sed -i "s/<\/assembly>/ <ms_asmv2:trustInfo xmlns:ms_asmv2=\"urn:schemas-microsoft-com:asm.v2\">\n <ms_asmv2:security>\n <ms_asmv2:requestedPrivileges>\n <ms_asmv2:requestedExecutionLevel level=\"AsInvoker\" uiAccess=\"false\"\/>\n <\/ms_asmv2:requestedPrivileges>\n <\/ms_asmv2:security>\n <\/ms_asmv2:trustInfo>\n<\/assembly>/" $CMANIFEST 2>/dev/null
+else
+ CMANIFEST=$ERL_TOP/erts/etc/win32/manifest.xml
+ MANIFEST=`w32_path.sh -d $CMANIFEST`
+fi
+if [ "$RES" = "0" ]; then
eval mt.exe -nologo -manifest "$MANIFEST" -outputresource:"$OUTPUTRES" >>/tmp/link.exe.${p}.1 2>>/tmp/link.exe.${p}.2
RES=$?
if [ "$RES" != "0" ]; then
@@ -192,7 +197,6 @@ if [ "$RES" = "0" -a -f "$CMANIFEST" ]; then
echo "If you get this error, make sure Windows Defender AND Windows Search is disabled">>/tmp/link.exe.${p}.1
rm -f "$CREMOVE"
fi
- rm -f "$CMANIFEST"
fi
# This works around some strange behaviour
diff --git a/erts/lib_src/Makefile.in b/erts/lib_src/Makefile.in
index 70ff666ed3..bb43d51d97 100644
--- a/erts/lib_src/Makefile.in
+++ b/erts/lib_src/Makefile.in
@@ -44,6 +44,7 @@ ERLANG_OSTYPE=@ERLANG_OSTYPE@
OMIT_FP=false
CFLAGS=$(subst O2,O3, @CFLAGS@)
+LDFLAGS=@LDFLAGS@
ifeq ($(TYPE),debug)
CFLAGS=@DEBUG_CFLAGS@ -DDEBUG
diff --git a/erts/vsn.mk b/erts/vsn.mk
index 7582350755..cc7958578f 100644
--- a/erts/vsn.mk
+++ b/erts/vsn.mk
@@ -18,7 +18,7 @@
# %CopyrightEnd%
#
-VSN = 11.1.3
+VSN = 11.1.8
# Port number 4365 in 4.2
# Port number 4366 in 4.3
diff --git a/lib/common_test/doc/src/Makefile b/lib/common_test/doc/src/Makefile
index cd5f2e2de0..a5f2f0975e 100644
--- a/lib/common_test/doc/src/Makefile
+++ b/lib/common_test/doc/src/Makefile
@@ -50,7 +50,8 @@ XML_REF3_FILES = ct.xml \
ct_property_test.xml \
ct_netconfc.xml \
ct_hooks.xml \
- ct_testspec.xml
+ ct_testspec.xml \
+ ct_suite.xml
XML_REF6_FILES = common_test_app.xml
XML_PART_FILES = part.xml
@@ -86,7 +87,7 @@ XML_FILES=$(XML_APPLICATION_FILES) $(XML_REF1_FILES) $(XML_REF3_FILES) $(XML_RE
TOP_SPECS_FILE = specs.xml
-NO_CHUNKS = ct_hooks.xml
+NO_CHUNKS = ct_hooks.xml ct_suite.xml
# ----------------------------------------------------
diff --git a/lib/common_test/doc/src/common_test_app.xml b/lib/common_test/doc/src/common_test_app.xml
index 3fcbda538a..07a2a3e2cd 100644
--- a/lib/common_test/doc/src/common_test_app.xml
+++ b/lib/common_test/doc/src/common_test_app.xml
@@ -52,564 +52,7 @@
<item>Step-by-step execution of test cases</item>
</list>
- <p>The following section describes the mandatory and optional test suite
- functions that <c>Common Test</c> calls during test execution.
- For more details, see section
- <seeguide marker="write_test_chapter">Writing Test Suites</seeguide>
- in the User's Guide.</p>
-
</description>
-
-
- <funcs>
- <fsdescription>
- <title>Test Case Callback Functions</title>
- <p>The following functions define the callback interface
- for a test suite.</p>
- </fsdescription>
- <func>
- <name since="">Module:all() -> Tests | {skip,Reason} </name>
- <fsummary>Returns the list of all test case groups and test cases
- in the module.</fsummary>
- <type>
- <v>Tests = [TestCase | {testcase,TestCase,TCRepeatProps} | {group,GroupName} | {group,GroupName,Properties} | {group,GroupName,Properties,SubGroups}]</v>
- <v>TestCase = atom()</v>
- <v>TCRepeatProps = [{repeat,N} | {repeat_until_ok,N} | {repeat_until_fail,N}]</v>
- <v>GroupName = atom()</v>
- <v>Properties = [parallel | sequence | Shuffle | {GroupRepeatType,N}] | default</v>
- <v>SubGroups = [{GroupName,Properties} | {GroupName,Properties,SubGroups}]</v>
- <v>Shuffle = shuffle | {shuffle,Seed}</v>
- <v>Seed = {integer(),integer(),integer()}</v>
- <v>GroupRepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | repeat_until_any_ok | repeat_until_any_fail</v>
- <v>N = integer() | forever</v>
- <v>Reason = term()</v>
- </type>
-
- <desc>
- <p>MANDATORY</p>
-
- <p>Returns the list of all test cases and test case groups in the
- test suite module to be executed. This list also specifies the
- order the cases and groups are executed by <c>Common Test</c>.
- A test case is represented by an atom,
- the name of the test case function, or a <c>testcase</c> tuple
- indicating that the test case shall be repeated. A test case group is
- represented by a <c>group</c> tuple, where <c>GroupName</c>,
- an atom, is the name of the group (defined in
- <seemfa marker="#Module:groups/0"><c>groups/0</c></seemfa>).
- Execution properties for groups can also be specified, both
- for a top-level group and for any of its subgroups.
- Group execution properties specified here override
- properties in the group definition (see
- <seemfa marker="#Module:groups/0"><c>groups/0</c></seemfa>).
- (With value <c>default</c>, the group definition properties
- are used).</p>
-
- <p>If <c>{skip,Reason}</c> is returned, all test cases
- in the module are skipped and <c>Reason</c>
- is printed on the HTML result page.</p>
-
- <p>For details on groups, see section
- <seeguide marker="write_test_chapter#test_case_groups">Test Case
- Groups</seeguide> in the User's Guide.</p>
-
- </desc>
- </func>
-
- <func>
- <name since="">Module:groups() -> GroupDefs</name>
- <fsummary>Returns a list of test case group definitions.</fsummary>
- <type>
- <v>GroupDefs = [Group]</v>
- <v>Group = {GroupName,Properties,GroupsAndTestCases}</v>
- <v>GroupName = atom()</v>
- <v>Properties = [parallel | sequence | Shuffle | {GroupRepeatType,N}]</v>
- <v>GroupsAndTestCases = [Group | {group,GroupName} | TestCase | {testcase,TestCase,TCRepeatProps}]</v>
- <v>TestCase = atom()</v>
- <v>TCRepeatProps = [{repeat,N} | {repeat_until_ok,N} | {repeat_until_fail,N}]</v>
- <v>Shuffle = shuffle | {shuffle,Seed}</v>
- <v>Seed = {integer(),integer(),integer()}</v>
- <v>GroupRepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | repeat_until_any_ok | repeat_until_any_fail</v>
- <v>N = integer() | forever</v>
- </type>
-
- <desc>
- <p>OPTIONAL</p>
-
- <p>Defines test case groups. For details, see section
- <seeguide marker="write_test_chapter#test_case_groups">Test Case
- Groups</seeguide> in the User's Guide.</p>
- </desc>
- </func>
-
- <func>
- <name since="">Module:suite() -> [Info] </name>
- <fsummary>Test suite info function (providing default data
- for the suite).</fsummary>
- <type>
- <v>Info = {timetrap,Time} | {require,Required} | {require,Name,Required} | {userdata,UserData} | {silent_connections,Conns} | {stylesheet,CSSFile} | {ct_hooks, CTHs}</v>
- <v>Time = TimeVal | TimeFunc</v>
- <v>TimeVal = MilliSec | {seconds,integer()} | {minutes,integer()} | {hours,integer()}</v>
- <v>TimeFunc = {Mod,Func,Args} | Fun</v>
- <v>MilliSec = integer()</v>
- <v>Mod = atom()</v>
- <v>Func = atom()</v>
- <v>Args = list()</v>
- <v>Fun = fun()</v>
- <v>Required = Key | {Key,SubKeys} | {Key,SubKey} | {Key,SubKey,SubKeys}</v>
- <v>Key = atom()</v>
- <v>SubKeys = SubKey | [SubKey]</v>
- <v>SubKey = atom()</v>
- <v>Name = atom()</v>
- <v>UserData = term()</v>
- <v>Conns = [atom()]</v>
- <v>CSSFile = string()</v>
- <v>CTHs = [CTHModule |</v>
- <v>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{CTHModule, CTHInitArgs} |</v>
- <v>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{CTHModule, CTHInitArgs, CTHPriority}]</v>
- <v>CTHModule = atom()</v>
- <v>CTHInitArgs = term()</v>
- </type>
- <desc>
-
- <p>OPTIONAL</p>
-
- <p>The test suite information function. Returns a list of tagged
- tuples specifying various properties related to the execution of
- this test suite (common for all test cases in the suite).</p>
-
- <p>Tag <c>timetrap</c> sets the maximum time that each
- test case is allowed to execute (including
- <seemfa marker="#Module:init_per_testcase/2"><c>init_per_testcase/2</c></seemfa>
- and
- <seemfa marker="#Module:end_per_testcase/2"><c>end_per_testcase/2</c></seemfa>).
- If the timetrap time is exceeded, the test case fails with reason
- <c>timetrap_timeout</c>. A <c>TimeFunc</c> function can be used to
- set a new timetrap by returning a <c>TimeVal</c>. It can also be
- used to trigger a timetrap time-out by, at some point, returning a
- value other than a <c>TimeVal</c>. For details, see section
- <seeguide marker="write_test_chapter#timetraps">Timetrap Time-Outs</seeguide>
- in the User's Guide.</p>
-
- <p>Tag <c>require</c> specifies configuration variables
- required by test cases (or configuration functions)
- in the suite. If the required configuration variables are not found
- in any of the configuration files, all test cases are skipped.
- For details about the <c>require</c> functionality, see funtion
- <seemfa marker="ct#require/1"><c>ct:require/1,2</c></seemfa>.</p>
-
- <p>With <c>userdata</c>, the user can
- specify any test suite-related information, which can be
- read by calling
- <seemfa marker="ct#userdata/2"><c>ct:userdata/2</c></seemfa>.</p>
-
- <p>Tag <c>ct_hooks</c> specifies the
- <seeguide marker="ct_hooks_chapter">Common Test Hooks</seeguide>
- to be run with this suite.</p>
-
- <p>Other tuples than the ones defined are ignored.</p>
-
- <p>For details about the test suite information function, see section
- <seeguide marker="write_test_chapter#suite">Test
- Suite Information Function</seeguide> in the User's Guide.</p>
- </desc>
- </func>
-
- <func>
- <name since="">Module:init_per_suite(Config) -> NewConfig | {skip,Reason} |
- {skip_and_save,Reason,SaveConfig}</name>
- <fsummary>Test suite initializations.</fsummary>
- <type>
- <v>Config = NewConfig = SaveConfig = [{Key,Value}]</v>
- <v>Key = atom()</v>
- <v>Value = term()</v>
- <v>Reason = term()</v>
- </type>
- <desc>
-
- <p>OPTIONAL; if this function is defined, then <seemfa
- marker="#Module:end_per_suite/1"><c>end_per_suite/1</c></seemfa>
- must also be defined.</p>
-
- <p>This configuration function is called as the first function in the
- suite. It typically contains initializations that are common for
- all test cases in the suite, and that must only be done
- once. Parameter <c>Config</c> is the configuration data
- that can be modified. Whatever is returned from this
- function is specified as <c>Config</c> to all configuration functions
- and test cases in the suite.</p>
-
- <p>If <c>{skip,Reason}</c>
- is returned, all test cases in the suite are skipped
- and <c>Reason</c> is printed in the overview log for the suite.</p>
-
- <p>For information on <c>save_config</c> and <c>skip_and_save</c>,
- see section
- <seeguide marker="dependencies_chapter#save_config">Saving
- Configuration Data</seeguide> in the User's Guide.</p>
- </desc>
- </func>
-
- <func>
- <name since="">Module:end_per_suite(Config) -> term() |
- {save_config,SaveConfig}</name>
- <fsummary>Test suite finalization.</fsummary>
- <type>
- <v>Config = SaveConfig = [{Key,Value}]</v>
- <v>Key = atom()</v>
- <v>Value = term()</v>
- </type>
-
- <desc>
- <p>OPTIONAL; if this function is defined, then <seemfa
- marker="#Module:init_per_suite/1"><c>init_per_suite/1</c></seemfa>
- must also be defined.</p>
-
- <p>This function is called as the last test case in the
- suite. It is meant to be used for cleaning up after
- <seemfa marker="#Module:init_per_suite/1"><c>init_per_suite/1</c></seemfa>.</p>
- <p>For information on <c>save_config</c>, see section
- <seeguide marker="dependencies_chapter#save_config">Saving
- Configuration Data</seeguide> in the User's Guide.</p>
- </desc>
- </func>
-
- <func>
- <name since="OTP R15B">Module:group(GroupName) -> [Info] </name>
- <fsummary>Test case group information function (providing default data
- for a test case group, that is, its test cases and
- subgroups).</fsummary>
- <type>
- <v>Info = {timetrap,Time} | {require,Required} | {require,Name,Required} | {userdata,UserData} | {silent_connections,Conns} | {stylesheet,CSSFile} | {ct_hooks, CTHs}</v>
- <v>Time = TimeVal | TimeFunc</v>
- <v>TimeVal = MilliSec | {seconds,integer()} | {minutes,integer()} | {hours,integer()}</v>
- <v>TimeFunc = {Mod,Func,Args} | Fun</v>
- <v>MilliSec = integer()</v>
- <v>Mod = atom()</v>
- <v>Func = atom()</v>
- <v>Args = list()</v>
- <v>Fun = fun()</v>
- <v>Required = Key | {Key,SubKeys} | {Key,Subkey} | {Key,Subkey,SubKeys}</v>
- <v>Key = atom()</v>
- <v>SubKeys = SubKey | [SubKey]</v>
- <v>SubKey = atom()</v>
- <v>Name = atom()</v>
- <v>UserData = term()</v>
- <v>Conns = [atom()]</v>
- <v>CSSFile = string()</v>
- <v>CTHs = [CTHModule |</v>
- <v>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{CTHModule, CTHInitArgs} |</v>
- <v>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{CTHModule, CTHInitArgs, CTHPriority}]</v>
- <v>CTHModule = atom()</v>
- <v>CTHInitArgs = term()</v>
- </type>
- <desc>
-
- <p>OPTIONAL</p>
-
- <p>The test case group information function. It is supposed to
- return a list of tagged tuples that specify various properties
- related to the execution of a test case group (that is, its test
- cases and subgroups). Properties set by
- <seemfa marker="#Module:group/1"><c>group/1</c></seemfa> override
- properties with the same key that have been set previously by
- <seemfa marker="#Module:suite/0"><c>suite/0</c></seemfa>.</p>
-
- <p>Tag <c>timetrap</c> sets the maximum time that each
- test case is allowed to execute (including
- <seemfa marker="#Module:init_per_testcase/2"><c>init_per_testcase/2</c></seemfa>
- and
- <seemfa marker="#Module:end_per_testcase/2"><c>end_per_testcase/2</c></seemfa>).
- If the timetrap time is
- exceeded, the test case fails with reason
- <c>timetrap_timeout</c>. A <c>TimeFunc</c> function can be used to
- set a new timetrap by returning a <c>TimeVal</c>. It can also be
- used to trigger a timetrap time-out by, at some point, returning a
- value other than a <c>TimeVal</c>. For details, see section
- <seeguide marker="write_test_chapter#timetraps">Timetrap
- Time-Outs</seeguide> in the User's Guide.</p>
-
- <p>Tag <c>require</c> specifies configuration variables
- required by test cases (or configuration functions)
- in the suite. If the required configuration variables are not found
- in any of the configuration files, all test cases in this group are
- skipped. For details about the <c>require</c> functionality, see
- function
- <seemfa marker="ct#require/1"><c>ct:require/1,2</c></seemfa>.</p>
-
- <p>With <c>userdata</c>, the user can
- specify any test case group related information that can be
- read by calling
- <seemfa marker="ct#userdata/2"><c>ct:userdata/2</c></seemfa>.</p>
-
- <p>Tag <c>ct_hooks</c> specifies the
- <seeguide marker="ct_hooks_chapter">Common Test Hooks</seeguide>
- to be run with this suite.</p>
-
- <p>Other tuples than the ones defined are ignored.</p>
-
- <p>For details about the test case group information function,
- see section <seeguide marker="write_test_chapter#group_info">Group
- Information Function</seeguide> in the User's Guide.</p>
- </desc>
- </func>
-
- <func>
- <name since="">Module:init_per_group(GroupName, Config) -> NewConfig |
- {skip,Reason}</name>
- <fsummary>Test case group initializations.</fsummary>
- <type>
- <v>GroupName = atom()</v>
- <v>Config = NewConfig = [{Key,Value}]</v>
- <v>Key = atom()</v>
- <v>Value = term()</v>
- <v>Reason = term()</v>
- </type>
- <desc>
-
- <p>OPTIONAL; if this function is defined, then <seemfa
- marker="#Module:end_per_group/2"><c>end_per_group/2</c></seemfa>
- must also be defined.</p>
-
- <p>This configuration function is called before execution of a
- test case group. It typically contains initializations that are
- common for all test cases and subgroups in the group, and that
- must only be performed once. <c>GroupName</c> is the name of the
- group, as specified in the group definition (see
- <seemfa marker="#Module:groups/0"><c>groups/0</c></seemfa>).
- Parameter <c>Config</c> is the configuration data that can be
- modified.
- The return value of this function is given as <c>Config</c>
- to all test cases and subgroups in the group.</p>
-
- <p>If <c>{skip,Reason}</c>
- is returned, all test cases in the group are skipped and
- <c>Reason</c> is printed in the overview log for the group.</p>
-
- <p>For information about test case groups, see section
- <seeguide marker="write_test_chapter#test_case_groups">Test Case
- Groups</seeguide> in the User's Guide.</p>
- </desc>
- </func>
-
- <func>
- <name since="">Module:end_per_group(GroupName, Config) -> term() |
- {return_group_result,Status}</name>
- <fsummary>Test case group finalization.</fsummary>
- <type>
- <v>GroupName = atom()</v>
- <v>Config = [{Key,Value}]</v>
- <v>Key = atom()</v>
- <v>Value = term()</v>
- <v>Status = ok | skipped | failed</v>
- </type>
-
- <desc>
- <p>OPTIONAL; if this function is defined, then <seemfa
- marker="#Module:init_per_group/2"><c>init_per_group/2</c></seemfa>
- must also be defined.</p>
-
- <p>This function is called after the execution of a test case group
- is finished. It is meant to be used for cleaning up after
- <seemfa marker="#Module:init_per_group/2"><c>init_per_group/2</c></seemfa>.
- A status value for a nested subgroup can be returned with
- <c>{return_group_result,Status}</c>. The status can be retrieved in
- <seemfa marker="#Module:end_per_group/2"><c>end_per_group/2</c></seemfa>
- for the group on the level above. The status is also used by
- <c>Common Test</c> for deciding if execution of a group is to
- proceed if property <c>sequence</c> or <c>repeat_until_*</c>
- is set.</p>
-
- <p>For details about test case groups, see section
- <seeguide marker="write_test_chapter#test_case_groups">Test Case
- Groups</seeguide> in the User's Guide.</p>
- </desc>
- </func>
-
- <func>
- <name since="">Module:init_per_testcase(TestCase, Config) -> NewConfig | {fail,Reason} | {skip,Reason}</name>
- <fsummary>Test case initializations.</fsummary>
- <type>
- <v> TestCase = atom()</v>
- <v> Config = NewConfig = [{Key,Value}]</v>
- <v> Key = atom()</v>
- <v> Value = term()</v>
- <v> Reason = term()</v>
- </type>
- <desc>
-
- <p>OPTIONAL; if this function is defined,
- then <seemfa marker="#Module:end_per_testcase/2">
- <c>end_per_testcase/2</c></seemfa> must also be
- defined.</p>
-
- <p>This function is called before each test case. Argument
- <c>TestCase</c> is the test case name, and
- <c>Config</c> (list of key-value tuples) is the configuration
- data that can be modified. The <c>NewConfig</c> list returned
- from this function is given as <c>Config</c> to the test case.
- If <c>{fail,Reason}</c> is returned, the test case is
- marked as failed without being executed.</p>
-
- <p>If <c>{skip,Reason}</c> is returned, the test case is skipped
- and <c>Reason</c> is printed in the overview log for the suite.</p>
- </desc>
- </func>
-
- <func>
- <name since="">Module:end_per_testcase(TestCase, Config) -> term() | {fail,Reason} | {save_config,SaveConfig}</name>
- <fsummary>Test case finalization.</fsummary>
- <type>
- <v>TestCase = atom()</v>
- <v>Config = SaveConfig = [{Key,Value}]</v>
- <v>Key = atom()</v>
- <v>Value = term()</v>
- <v>Reason = term()</v>
- </type>
- <desc>
-
- <p>OPTIONAL; if this function is defined,
- then <seemfa marker="#Module:init_per_testcase/2">
- <c>init_per_testcase/2</c></seemfa> must also be
- defined.</p>
-
- <p>This function is called after each test case, and can be used
- to clean up after
- <seemfa marker="#Module:init_per_testcase/2"><c>init_per_testcase/2</c></seemfa>
- and the test case. Any return value (besides <c>{fail,Reason}</c>
- and <c>{save_config,SaveConfig}</c>) is ignored. By returning
- <c>{fail,Reason}</c>, <c>TestCase</c> is marked as faulty (even
- though it was successful in the sense that it returned
- a value instead of terminating).</p>
-
- <p>For information on <c>save_config</c>, see section
- <seeguide marker="dependencies_chapter#save_config">Saving
- Configuration Data</seeguide> in the User's Guide.</p>
- </desc>
- </func>
-
- <func>
- <name since="OTP R14B">Module:Testcase() -> [Info] </name>
- <fsummary>Test case information function.</fsummary>
- <type>
- <v>Info = {timetrap,Time} | {require,Required} | {require,Name,Required} | {userdata,UserData} | {silent_connections,Conns}</v>
- <v>Time = TimeVal | TimeFunc</v>
- <v>TimeVal = MilliSec | {seconds,integer()} | {minutes,integer()} | {hours,integer()}</v>
- <v>TimeFunc = {Mod,Func,Args} | Fun</v>
- <v>MilliSec = integer()</v>
- <v>Mod = atom()</v>
- <v>Func = atom()</v>
- <v>Args = list()</v>
- <v>Fun = fun()</v>
- <v>Required = Key | {Key,SubKeys} | {Key,Subkey} | {Key,Subkey,SubKeys}</v>
- <v>Key = atom()</v>
- <v>SubKeys = SubKey | [SubKey]</v>
- <v>SubKey = atom()</v>
- <v>Name = atom()</v>
- <v>UserData = term()</v>
- <v>Conns = [atom()]</v>
- </type>
-
- <desc>
-
- <p>OPTIONAL</p>
-
- <p>The test case information function. It is supposed to
- return a list of tagged tuples that specify various properties
- related to the execution of this particular test case.
- Properties set by
- <seemfa marker="#Module:Testcase/0"><c>Testcase/0</c></seemfa>
- override properties set previously for the test case by
- <seemfa marker="#Module:group/1"><c>group/1</c></seemfa> or
- <seemfa marker="#Module:suite/0"><c>suite/0</c></seemfa>.</p>
-
- <p>Tag <c>timetrap</c> sets the maximum time that the
- test case is allowed to execute. If the timetrap time is
- exceeded, the test case fails with reason <c>timetrap_timeout</c>.
- <seemfa marker="#Module:init_per_testcase/2"><c>init_per_testcase/2</c></seemfa>
- and
- <seemfa marker="#Module:end_per_testcase/2"><c>end_per_testcase/2</c></seemfa>
- are included in the timetrap time.
- A <c>TimeFunc</c> function can be used to
- set a new timetrap by returning a <c>TimeVal</c>. It can also be
- used to trigger a timetrap time-out by, at some point, returning a
- value other than a <c>TimeVal</c>. For details, see section
- <seeguide marker="write_test_chapter#timetraps">Timetrap
- Time-Outs</seeguide> in the User's Guide.</p>
-
- <p>Tag <c>require</c> specifies configuration variables
- that are required by the test case (or <c>init_per_testcase/2</c>
- or <c>end_per_testcase/2</c>).
- If the required configuration variables are not found in any of the
- configuration files, the test case is skipped. For details about
- the <c>require</c> functionality, see function
- <seemfa marker="ct#require/1"><c>ct:require/1,2</c></seemfa>.</p>
-
- <p>If <c>timetrap</c> or <c>require</c> is not set, the
- default values specified by
- <seemfa marker="#Module:suite/0"><c>suite/0</c></seemfa> (or
- <seemfa marker="#Module:group/1"><c>group/1</c></seemfa>) are used.</p>
-
- <p>With <c>userdata</c>, the user can specify any test case-related
- information that can be read by calling
- <seemfa marker="ct#userdata/3"><c>ct:userdata/3</c></seemfa>.</p>
-
- <p>Other tuples than the ones defined are ignored.</p>
-
- <p>For details about the test case information function, see section
- <seeguide marker="write_test_chapter#info_function">Test
- Case Information Function</seeguide> in the User's Guide.</p>
- </desc>
- </func>
-
- <func>
- <name since="OTP R14B">Module:Testcase(Config) -> term() | {skip,Reason} | {comment,Comment} | {save_config,SaveConfig} | {skip_and_save,Reason,SaveConfig} | exit() </name>
- <fsummary>A test case.</fsummary>
- <type>
- <v>Config = SaveConfig = [{Key,Value}]</v>
- <v>Key = atom()</v>
- <v>Value = term()</v>
- <v>Reason = term()</v>
- <v>Comment = string()</v>
- </type>
-
- <desc>
- <p>MANDATORY</p>
-
- <p>The implementation of a test case. Call the functions to test and
- check the result. If something fails, ensure the
- function causes a runtime error or call
- <seemfa marker="ct#fail/1"><c>ct:fail/1,2</c></seemfa>
- (which also causes the test case process to terminate).</p>
-
- <p>Elements from the <c>Config</c> list can, for example, be read
- with <c>proplists:get_value/2</c> in STDLIB
- (or the macro <c>?config</c> defined in <c>ct.hrl</c>).</p>
-
- <p>If you decide not to run the test case after all, return
- <c>{skip,Reason}</c>. <c>Reason</c> is then
- printed in field <c>Comment</c> on the HTML result page.</p>
-
- <p>To print some information in field <c>Comment</c> on the HTML
- result page, return <c>{comment,Comment}</c>.</p>
-
- <p>If the function returns anything else, the test case is
- considered successful. The return value always gets printed
- in the test case log file.</p>
-
- <p>For details about test case implementation, see section
- <seeguide marker="write_test_chapter#test_cases">Test Cases</seeguide>
- in the User's Guide.</p>
-
- <p>For information on <c>save_config</c> and <c>skip_and_save</c>,
- see section
- <seeguide marker="dependencies_chapter#save_config">Saving
- Configuration Data</seeguide> in the User's Guide.</p>
- </desc>
- </func>
-
- </funcs>
-
</erlref>
diff --git a/lib/common_test/doc/src/ct.xml b/lib/common_test/doc/src/ct.xml
index 0d1c807697..c1f638b580 100644
--- a/lib/common_test/doc/src/ct.xml
+++ b/lib/common_test/doc/src/ct.xml
@@ -57,9 +57,9 @@
<item><p><c>data_dir</c> - Data file directory</p></item>
<item><p><c>priv_dir</c> - Scratch file directory</p></item>
<item><p>Whatever added by
- <seemfa marker="common_test#Module:init_per_suite/1"><c>init_per_suite/1</c></seemfa>
+ <seemfa marker="ct_suite#Module:init_per_suite/1"><c>init_per_suite/1</c></seemfa>
or
- <seemfa marker="common_test#Module:init_per_testcase/2"><c>init_per_testcase/2</c></seemfa>
+ <seemfa marker="ct_suite#Module:init_per_testcase/2"><c>init_per_testcase/2</c></seemfa>
in the test suite.</p></item>
</list>
@@ -1524,7 +1524,7 @@
<desc><marker id="userdata-2"/>
<p>Returns any data specified with tag <c>userdata</c> in the list
of tuples returned from
- <seemfa marker="common_test#Module:suite/0"><c>suite/0</c></seemfa>.</p>
+ <seemfa marker="ct_suite#Module:suite/0"><c>suite/0</c></seemfa>.</p>
</desc>
</func>
diff --git a/lib/common_test/doc/src/ct_hooks.xml b/lib/common_test/doc/src/ct_hooks.xml
index 3c38dcd439..875efec305 100644
--- a/lib/common_test/doc/src/ct_hooks.xml
+++ b/lib/common_test/doc/src/ct_hooks.xml
@@ -130,12 +130,12 @@
<p>OPTIONAL</p>
<p>This function is called after
- <seemfa marker="common_test#Module:groups/0"><c>groups/0</c></seemfa>.
+ <seemfa marker="ct_suite#Module:groups/0"><c>groups/0</c></seemfa>.
It is used to modify the test group definitions, for
instance to add or remove groups or change group properties.</p>
<p><c>GroupDefs</c> is what
- <seemfa marker="common_test#Module:groups/0"><c>groups/0</c></seemfa>
+ <seemfa marker="ct_suite#Module:groups/0"><c>groups/0</c></seemfa>
returned, that is, a list of group definitions.</p>
<p><c>NewGroupDefs</c> is the possibly modified version of this list.</p>
@@ -146,7 +146,7 @@
in the User's Guide.</p>
<p>Notice that for CTHs that are installed by means of the
- <seemfa marker="common_test#Module:suite/0"><c>suite/0</c></seemfa>
+ <seemfa marker="ct_suite#Module:suite/0"><c>suite/0</c></seemfa>
function, <c>post_groups/2</c> is called before
the <seemfa marker="#Module:init/2"><c>init/2</c></seemfa>
hook function. However, for CTHs that are installed by means
@@ -191,19 +191,19 @@
<p>OPTIONAL</p>
<p>This function is called after
- <seemfa marker="common_test#Module:all/0"><c>all/0</c></seemfa>.
+ <seemfa marker="ct_suite#Module:all/0"><c>all/0</c></seemfa>.
It is used to modify the set of test cases and test group to
be executed, for instance to add or remove test cases and
groups, change group properties, or even skip all tests in
the suite.</p>
<p><c>Return</c> is what
- <seemfa marker="common_test#Module:all/0"><c>all/0</c></seemfa>
+ <seemfa marker="ct_suite#Module:all/0"><c>all/0</c></seemfa>
returned, that is, a list of test cases and groups to be
executed, or a tuple <c>{skip,Reason}</c>.</p>
<p><c>GroupDefs</c> is what
- <seemfa marker="common_test#Module:groups/0"><c>groups/0</c></seemfa>
+ <seemfa marker="ct_suite#Module:groups/0"><c>groups/0</c></seemfa>
or the <c>post_groups/2</c> hook returned, that is, a list
of group definitions.</p>
@@ -215,7 +215,7 @@
in the User's Guide.</p>
<p>Notice that for CTHs that are installed by means of the
- <seemfa marker="common_test#Module:suite/0"><c>suite/0</c></seemfa>
+ <seemfa marker="ct_suite#Module:suite/0"><c>suite/0</c></seemfa>
function, <c>post_all/2</c> is called before
the <seemfa marker="#Module:init/2"><c>init/2</c></seemfa>
hook function. However, for CTHs that are installed by means
@@ -253,7 +253,7 @@
<p>OPTIONAL</p>
<p>This function is called before
- <seemfa marker="common_test#Module:init_per_suite/1"><c>init_per_suite</c></seemfa>
+ <seemfa marker="ct_suite#Module:init_per_suite/1"><c>init_per_suite</c></seemfa>
if it exists. It typically contains initialization/logging that must
be done before <c>init_per_suite</c> is called. If
<c>{skip,Reason}</c> or <c>{fail,Reason}</c> is returned,
@@ -270,11 +270,11 @@
<p><c>Return</c> is the result of the <c>init_per_suite</c> function.
If it is <c>{skip,Reason}</c> or <c>{fail,Reason}</c>,
- <seemfa marker="common_test#Module:init_per_suite/1"><c>init_per_suite</c></seemfa>
+ <seemfa marker="ct_suite#Module:init_per_suite/1"><c>init_per_suite</c></seemfa>
is never called, instead the initiation is considered to be
skipped or failed, respectively. If a <c>NewConfig</c> list is
returned,
- <seemfa marker="common_test#Module:init_per_suite/1"><c>init_per_suite</c></seemfa>
+ <seemfa marker="ct_suite#Module:init_per_suite/1"><c>init_per_suite</c></seemfa>
is called with that <c>NewConfig</c> list. For more details, see
section <seeguide marker="ct_hooks_chapter#pre">Pre Hooks</seeguide>
in the User's Guide.</p>
@@ -304,21 +304,21 @@
<p>OPTIONAL</p>
<p>This function is called after
- <seemfa marker="common_test#Module:init_per_suite/1"><c>init_per_suite</c></seemfa>
+ <seemfa marker="ct_suite#Module:init_per_suite/1"><c>init_per_suite</c></seemfa>
if it exists. It typically contains extra checks to ensure that all
the correct dependencies are started correctly.</p>
<p><c>Return</c> is what
- <seemfa marker="common_test#Module:init_per_suite/1"><c>init_per_suite</c></seemfa>
+ <seemfa marker="ct_suite#Module:init_per_suite/1"><c>init_per_suite</c></seemfa>
returned, that is, <c>{fail,Reason}</c>, <c>{skip,Reason}</c>, a
<c>Config</c> list, or a term describing how
- <seemfa marker="common_test#Module:init_per_suite/1"><c>init_per_suite</c></seemfa>
+ <seemfa marker="ct_suite#Module:init_per_suite/1"><c>init_per_suite</c></seemfa>
failed.</p>
<p><c>NewReturn</c> is the possibly modified return value of
- <seemfa marker="common_test#Module:init_per_suite/1"><c>init_per_suite</c></seemfa>.
+ <seemfa marker="ct_suite#Module:init_per_suite/1"><c>init_per_suite</c></seemfa>.
To recover from a failure in
- <seemfa marker="common_test#Module:init_per_suite/1"><c>init_per_suite</c></seemfa>,
+ <seemfa marker="ct_suite#Module:init_per_suite/1"><c>init_per_suite</c></seemfa>,
return <c>ConfigList</c> with the <c>tc_status</c> element removed.
For more details, see
<seeguide marker="ct_hooks_chapter#post"> Post Hooks</seeguide> in
@@ -352,11 +352,11 @@
<p>OPTIONAL</p>
<p>This function is called before
- <seemfa marker="common_test#Module:init_per_group/2"><c>init_per_group</c></seemfa>
+ <seemfa marker="ct_suite#Module:init_per_group/2"><c>init_per_group</c></seemfa>
if it exists. It behaves the same way as
<seemfa marker="ct_hooks#Module:pre_init_per_suite/3"><c>pre_init_per_suite</c></seemfa>,
but for function
- <seemfa marker="common_test#Module:init_per_group/2"><c>init_per_group</c></seemfa>
+ <seemfa marker="ct_suite#Module:init_per_group/2"><c>init_per_group</c></seemfa>
instead.</p>
<p>If <c>Module:pre_init_per_group/4</c> is not exported, common_test
@@ -385,11 +385,11 @@
<p>OPTIONAL</p>
<p>This function is called after
- <seemfa marker="common_test#Module:init_per_group/2"><c>init_per_group</c></seemfa>
+ <seemfa marker="ct_suite#Module:init_per_group/2"><c>init_per_group</c></seemfa>
if it exists. It behaves the same way as
<seemfa marker="ct_hooks#Module:post_init_per_suite/4"><c>post_init_per_suite</c></seemfa>,
but for function
- <seemfa marker="common_test#Module:init_per_group/2"><c>init_per_group</c></seemfa>
+ <seemfa marker="ct_suite#Module:init_per_group/2"><c>init_per_group</c></seemfa>
instead.</p>
<p>If <c>Module:post_init_per_group/5</c> is not exported, common_test
@@ -418,11 +418,11 @@
<p>OPTIONAL</p>
<p>This function is called before
- <seemfa marker="common_test#Module:init_per_testcase/2"><c>init_per_testcase</c></seemfa>
+ <seemfa marker="ct_suite#Module:init_per_testcase/2"><c>init_per_testcase</c></seemfa>
if it exists. It behaves the same way as
<seemfa marker="ct_hooks#Module:pre_init_per_suite/3"><c>pre_init_per_suite</c></seemfa>,
but for function
- <seemfa marker="common_test#Module:init_per_testcase/2"><c>init_per_testcase</c></seemfa>
+ <seemfa marker="ct_suite#Module:init_per_testcase/2"><c>init_per_testcase</c></seemfa>
instead.</p>
<p>If <c>Module:pre_init_per_testcase/4</c> is not exported, common_test
@@ -455,11 +455,11 @@
<p>OPTIONAL</p>
<p>This function is called after
- <seemfa marker="common_test#Module:init_per_testcase/2"><c>init_per_testcase</c></seemfa>
+ <seemfa marker="ct_suite#Module:init_per_testcase/2"><c>init_per_testcase</c></seemfa>
if it exists. It behaves the same way as
<seemfa marker="ct_hooks#Module:post_init_per_suite/4"><c>post_init_per_suite</c></seemfa>,
but for function
- <seemfa marker="common_test#Module:init_per_testcase/2"><c>init_per_testcase</c></seemfa>
+ <seemfa marker="ct_suite#Module:init_per_testcase/2"><c>init_per_testcase</c></seemfa>
instead.</p>
<p>If <c>Module:post_init_per_testcase/5</c> is not exported, common_test
@@ -487,11 +487,11 @@
<p>OPTIONAL</p>
<p>This function is called before
- <seemfa marker="common_test#Module:end_per_testcase/2"><c>end_per_testcase</c></seemfa>
+ <seemfa marker="ct_suite#Module:end_per_testcase/2"><c>end_per_testcase</c></seemfa>
if it exists. It behaves the same way as
<seemfa marker="ct_hooks#Module:pre_end_per_suite/3"><c>pre_end_per_suite</c></seemfa>,
but for function
- <seemfa marker="common_test#Module:end_per_testcase/2"><c>end_per_testcase</c></seemfa>
+ <seemfa marker="ct_suite#Module:end_per_testcase/2"><c>end_per_testcase</c></seemfa>
instead.</p>
<p>This function cannot change the result of the test case by returning skip or fail
@@ -524,11 +524,11 @@
<p>OPTIONAL</p>
<p>This function is called after
- <seemfa marker="common_test#Module:end_per_testcase/2"><c>end_per_testcase</c></seemfa>
+ <seemfa marker="ct_suite#Module:end_per_testcase/2"><c>end_per_testcase</c></seemfa>
if it exists. It behaves the same way as
<seemfa marker="ct_hooks#Module:post_end_per_suite/4"><c>post_end_per_suite</c></seemfa>,
but for function
- <seemfa marker="common_test#Module:end_per_testcase/2"><c>end_per_testcase</c></seemfa>
+ <seemfa marker="ct_suite#Module:end_per_testcase/2"><c>end_per_testcase</c></seemfa>
instead.</p>
<p>If <c>Module:post_end_per_testcase/5</c> is not exported, common_test
@@ -557,11 +557,11 @@
<p>OPTIONAL</p>
<p>This function is called before
- <seemfa marker="common_test#Module:end_per_group/2"><c>end_per_group</c></seemfa>
+ <seemfa marker="ct_suite#Module:end_per_group/2"><c>end_per_group</c></seemfa>
if it exists. It behaves the same way as
<seemfa marker="ct_hooks#Module:pre_init_per_suite/3"><c>pre_init_per_suite</c></seemfa>,
but for function
- <seemfa marker="common_test#Module:end_per_group/2"><c>end_per_group</c></seemfa>
+ <seemfa marker="ct_suite#Module:end_per_group/2"><c>end_per_group</c></seemfa>
instead.</p>
<p>If <c>Module:pre_end_per_group/4</c> is not exported, common_test
@@ -590,11 +590,11 @@
<p>OPTIONAL</p>
<p>This function is called after
- <seemfa marker="common_test#Module:end_per_group/2"><c>end_per_group</c></seemfa>
+ <seemfa marker="ct_suite#Module:end_per_group/2"><c>end_per_group</c></seemfa>
if it exists. It behaves the same way as
<seemfa marker="ct_hooks#Module:post_init_per_suite/4"><c>post_init_per_suite</c></seemfa>,
but for function
- <seemfa marker="common_test#Module:end_per_group/2">end_per_group</seemfa>
+ <seemfa marker="ct_suite#Module:end_per_group/2">end_per_group</seemfa>
instead.</p>
<p>If <c>Module:post_end_per_group/5</c> is not exported, common_test
@@ -622,11 +622,11 @@
<p>OPTIONAL</p>
<p>This function is called before
- <seemfa marker="common_test#Module:end_per_suite/1"><c>end_per_suite</c></seemfa>
+ <seemfa marker="ct_suite#Module:end_per_suite/1"><c>end_per_suite</c></seemfa>
if it exists. It behaves the same way as
<seemfa marker="ct_hooks#Module:pre_init_per_suite/3"><c>pre_init_per_suite</c></seemfa>,
but for function
- <seemfa marker="common_test#Module:end_per_suite/1"><c>end_per_suite</c></seemfa>
+ <seemfa marker="ct_suite#Module:end_per_suite/1"><c>end_per_suite</c></seemfa>
instead.</p>
</desc>
</func>
@@ -649,11 +649,11 @@
<p>OPTIONAL</p>
<p>This function is called after
- <seemfa marker="common_test#Module:end_per_suite/1"><c>end_per_suite</c></seemfa>
+ <seemfa marker="ct_suite#Module:end_per_suite/1"><c>end_per_suite</c></seemfa>
if it exists. It behaves the same way as
<seemfa marker="ct_hooks#Module:post_init_per_suite/4"><c>post_init_per_suite</c></seemfa>,
but for function
- <seemfa marker="common_test#Module:end_per_suite/1"><c>end_per_suite</c></seemfa>
+ <seemfa marker="ct_suite#Module:end_per_suite/1"><c>end_per_suite</c></seemfa>
instead.</p>
</desc>
</func>
diff --git a/lib/common_test/doc/src/ct_hooks_chapter.xml b/lib/common_test/doc/src/ct_hooks_chapter.xml
index e23a8b461a..a4c72c1fa4 100644
--- a/lib/common_test/doc/src/ct_hooks_chapter.xml
+++ b/lib/common_test/doc/src/ct_hooks_chapter.xml
@@ -84,10 +84,10 @@
<p>CTHs can also be added within a test suite. This is done by returning
<c>{ct_hooks,[CTH]}</c> in the configuration list from
- <seemfa marker="common_test#Module:suite/0">suite/0</seemfa>,
- <seemfa marker="common_test#Module:init_per_suite/1">
+ <seemfa marker="ct_suite#Module:suite/0">suite/0</seemfa>,
+ <seemfa marker="ct_suite#Module:init_per_suite/1">
init_per_suite/1</seemfa>, or
- <seemfa marker="common_test#Module:init_per_group/2">
+ <seemfa marker="ct_suite#Module:init_per_group/2">
init_per_group/2</seemfa>.</p>
<p>In this case, <c>CTH</c> can either be only the module name of the CTH
@@ -157,7 +157,7 @@
<cell>the last test suite has been run</cell>
</row>
<row>
- <cell><seemfa marker="common_test#Module:suite/0">suite/0
+ <cell><seemfa marker="ct_suite#Module:suite/0">suite/0
</seemfa></cell>
<cell><seemfa marker="ct_hooks#Module:pre_init_per_suite/3">
pre_init_per_suite/3</seemfa> is called</cell>
@@ -165,7 +165,7 @@
post_end_per_suite/4</seemfa> has been called for that test suite</cell>
</row>
<row>
- <cell><seemfa marker="common_test#Module:init_per_suite/1">
+ <cell><seemfa marker="ct_suite#Module:init_per_suite/1">
init_per_suite/1</seemfa></cell>
<cell><seemfa marker="ct_hooks#Module:post_init_per_suite/4">
post_init_per_suite/4</seemfa> is called</cell>
@@ -173,7 +173,7 @@
post_end_per_suite/4</seemfa> has been called for that test suite</cell>
</row>
<row>
- <cell><seemfa marker="common_test#Module:init_per_group/2">
+ <cell><seemfa marker="ct_suite#Module:init_per_group/2">
init_per_group/2</seemfa></cell>
<cell><seemfa marker="ct_hooks#Module:post_init_per_group/5">
post_init_per_group/5</seemfa> is called</cell>
@@ -236,12 +236,12 @@
In a CTH, the behavior can be hooked in before the following functions:</p>
<list type="bulleted">
- <item><seemfa marker="common_test#Module:init_per_suite/1"><c>init_per_suite</c></seemfa></item>
- <item><seemfa marker="common_test#Module:init_per_group/2"><c>init_per_group</c></seemfa></item>
- <item><seemfa marker="common_test#Module:init_per_testcase/2"><c>init_per_testcase</c></seemfa></item>
- <item><seemfa marker="common_test#Module:end_per_testcase/2"><c>end_per_testcase</c></seemfa></item>
- <item><seemfa marker="common_test#Module:end_per_group/2"><c>end_per_group</c></seemfa></item>
- <item><seemfa marker="common_test#Module:end_per_suite/1"><c>end_per_suite</c></seemfa></item>
+ <item><seemfa marker="ct_suite#Module:init_per_suite/1"><c>init_per_suite</c></seemfa></item>
+ <item><seemfa marker="ct_suite#Module:init_per_group/2"><c>init_per_group</c></seemfa></item>
+ <item><seemfa marker="ct_suite#Module:init_per_testcase/2"><c>init_per_testcase</c></seemfa></item>
+ <item><seemfa marker="ct_suite#Module:end_per_testcase/2"><c>end_per_testcase</c></seemfa></item>
+ <item><seemfa marker="ct_suite#Module:end_per_group/2"><c>end_per_group</c></seemfa></item>
+ <item><seemfa marker="ct_suite#Module:end_per_suite/1"><c>end_per_suite</c></seemfa></item>
</list>
<p>
@@ -282,12 +282,12 @@
<title>Post Hooks</title>
<p>In a CTH, behavior can be hooked in after the following functions:</p>
<list type="bulleted">
- <item><seemfa marker="common_test#Module:init_per_suite/1"><c>init_per_suite</c></seemfa></item>
- <item><seemfa marker="common_test#Module:init_per_group/2"><c>init_per_group</c></seemfa></item>
- <item><seemfa marker="common_test#Module:init_per_testcase/2"><c>init_per_testcase</c></seemfa></item>
- <item><seemfa marker="common_test#Module:end_per_testcase/2"><c>end_per_testcase</c></seemfa></item>
- <item><seemfa marker="common_test#Module:end_per_group/2"><c>end_per_group</c></seemfa></item>
- <item><seemfa marker="common_test#Module:end_per_suite/1"><c>end_per_suite</c></seemfa></item>
+ <item><seemfa marker="ct_suite#Module:init_per_suite/1"><c>init_per_suite</c></seemfa></item>
+ <item><seemfa marker="ct_suite#Module:init_per_group/2"><c>init_per_group</c></seemfa></item>
+ <item><seemfa marker="ct_suite#Module:init_per_testcase/2"><c>init_per_testcase</c></seemfa></item>
+ <item><seemfa marker="ct_suite#Module:end_per_testcase/2"><c>end_per_testcase</c></seemfa></item>
+ <item><seemfa marker="ct_suite#Module:end_per_group/2"><c>end_per_group</c></seemfa></item>
+ <item><seemfa marker="ct_suite#Module:end_per_suite/1"><c>end_per_suite</c></seemfa></item>
</list>
<p>
diff --git a/lib/common_test/doc/src/ct_property_test.xml b/lib/common_test/doc/src/ct_property_test.xml
index 891e0b475b..4c5eede758 100644
--- a/lib/common_test/doc/src/ct_property_test.xml
+++ b/lib/common_test/doc/src/ct_property_test.xml
@@ -186,7 +186,7 @@ prop_ftp() -&gt;
<d>the output from for example proper:run_commands/2 or proper:run_parallel_commands/2</d>
<v>Config =</v>
- <d>the Common Test <seemfa marker="common_test#Module:Testcase/1">Config</seemfa> in test cases.</d>
+ <d>the Common Test <seemfa marker="ct_suite#Module:Testcase/1">Config</seemfa> in test cases.</d>
<v>Options = [present_option()]</v>
<v>present_option() = {print_fun, fun(Format,Args)}</v>
diff --git a/lib/common_test/doc/src/ct_suite.xml b/lib/common_test/doc/src/ct_suite.xml
new file mode 100644
index 0000000000..0289a569f0
--- /dev/null
+++ b/lib/common_test/doc/src/ct_suite.xml
@@ -0,0 +1,619 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2020</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ The Initial Developer of the Original Code is Ericsson AB.
+ </legalnotice>
+ <title>ct_suite</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ </header>
+ <module since="">ct_suite</module>
+ <modulesummary>-behaviour(ct_suite).
+ </modulesummary>
+ <description>
+ <p>The following section describes the mandatory and optional test suite
+ functions that <c>Common Test</c> calls during test execution.
+ For more details, see section
+ <seeguide marker="write_test_chapter">Writing Test Suites</seeguide>
+ in the User's Guide.</p>
+ </description>
+
+ <datatypes>
+ <datatype>
+ <name name="ct_testname" n_vars="0"/>
+ <desc><p>The name of the testcase function.</p></desc>
+ </datatype>
+ <datatype>
+ <name name="ct_groupname" n_vars="0"/>
+ <desc><p>The name of the test group.</p></desc>
+ </datatype>
+ <datatype>
+ <name name="ct_config" n_vars="0"/>
+ <desc><p>The configuration data that can be modified.</p></desc>
+ </datatype>
+ <datatype>
+ <name name="ct_status" n_vars="0"/>
+ <desc><p>The status value for a nested subgroup.</p></desc>
+ </datatype>
+ <datatype>
+ <name>ct_group_def()</name>
+ <desc><p>The test group definition, as returned by <seemfa marker="#Module:groups/0"><c>Module:groups/0</c></seemfa>.</p></desc>
+ </datatype>
+ <datatype>
+ <name>ct_test_def()</name>
+ <desc><p>The test suite definition, as returned by <seemfa marker="#Module:all/0"><c>Module:all/0</c></seemfa>.</p></desc>
+ </datatype>
+ <datatype>
+ <name>ct_info()</name>
+ <desc><p>The test suite information, as returned by <seemfa marker="#Module:suite/0"><c>Module:suite/0</c></seemfa>, <seemfa marker="#Module:group/1"><c>Module:group/1</c></seemfa> and <seemfa marker="#Module:Testcase/0"><c>Module:Testcase/0</c></seemfa>.</p></desc>
+ </datatype>
+ </datatypes>
+
+ <funcs>
+ <fsdescription>
+ <title>Callback Functions</title>
+ <p>
+ The following functions are to be exported from a
+ <c>ct_suite</c> callback module in order to define
+ the callback interface for a test suite.
+ </p>
+ </fsdescription>
+
+ <func>
+ <name since="">Module:all() -> [ct_test_def()] | {skip, Reason}</name>
+ <fsummary>Returns the list of all test case groups and test cases
+ in the module.</fsummary>
+ <type>
+ <v><seetype marker="#ct_test_def">ct_test_def()</seetype> = TestCase | {group, GroupName} | {group, GroupName, Properties} | {group, GroupName, Properties, SubGroups}</v>
+ <v>TestCase = <seetype marker="#ct_testname">ct_testname()</seetype></v>
+ <v>GroupName = <seetype marker="#ct_groupname">ct_groupname()</seetype></v>
+ <v>Properties = [parallel | sequence | Shuffle | {RepeatType, N}] | default</v>
+ <v>SubGroups = [{GroupName, Properties} | {GroupName, Properties, SubGroups}]</v>
+ <v>Shuffle = shuffle | {shuffle, Seed}</v>
+ <v>Seed = {integer(), integer(), integer()}</v>
+ <v>RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | repeat_until_any_ok | repeat_until_any_fail</v>
+ <v>N = integer() | forever</v>
+ <v>Reason = term()</v>
+ </type>
+
+ <desc>
+ <p>MANDATORY</p>
+
+ <p>Returns the list of all test cases and test case groups in the
+ test suite module to be executed. This list also specifies the
+ order the cases and groups are executed by <c>Common Test</c>.
+ A test case is represented by an atom,
+ the name of the test case function, or a <c>testcase</c> tuple
+ indicating that the test case shall be repeated. A test case group is
+ represented by a <c>group</c> tuple, where <c>GroupName</c>,
+ an atom, is the name of the group (defined in
+ <seemfa marker="#Module:groups/0"><c>Module:groups/0</c></seemfa>).
+ Execution properties for groups can also be specified, both
+ for a top-level group and for any of its subgroups.
+ Group execution properties specified here override
+ properties in the group definition (see
+ <seemfa marker="#Module:groups/0"><c>Module:groups/0</c></seemfa>).
+ (With value <c>default</c>, the group definition properties
+ are used).</p>
+
+ <p>If <c>{skip, Reason}</c> is returned, all test cases
+ in the module are skipped and <c>Reason</c>
+ is printed on the HTML result page.</p>
+
+ <p>For details on groups, see section
+ <seeguide marker="write_test_chapter#test_case_groups">Test Case
+ Groups</seeguide> in the User's Guide.</p>
+
+ </desc>
+ </func>
+
+ <func>
+ <name since="">Module:groups() -> [ct_group_def()]</name>
+ <fsummary>Returns a list of test case group definitions.</fsummary>
+ <type>
+ <v><seetype marker="#ct_group_def">ct_group_def()</seetype> = {GroupName, Properties, GroupsAndTestCases}</v>
+ <v>GroupName = <seetype marker="#ct_groupname">ct_groupname()</seetype></v>
+ <v>Properties = [parallel | sequence | Shuffle | {RepeatType, N}]</v>
+ <v>GroupsAndTestCases = [Group | {group, GroupName} | TestCase]</v>
+ <v>TestCase = <seetype marker="#ct_testname">ct_testname()</seetype></v>
+ <v>Shuffle = shuffle | {shuffle, Seed}</v>
+ <v>Seed = {integer(), integer(), integer()}</v>
+ <v>RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | repeat_until_any_ok | repeat_until_any_fail</v>
+ <v>N = integer() | forever</v>
+ </type>
+
+ <desc>
+ <p>OPTIONAL</p>
+
+ <p>Defines test case groups. For details, see section
+ <seeguide marker="write_test_chapter#test_case_groups">Test Case
+ Groups</seeguide> in the User's Guide.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="">Module:suite() -> [ct_info()]</name>
+ <fsummary>Test suite info function (providing default data
+ for the suite).</fsummary>
+ <type>
+ <v><seetype marker="#ct_info">ct_info()</seetype> = {timetrap, Time} | {require, Required} | {require, Name, Required} | {userdata, UserData} | {silent_connections, Conns} | {stylesheet, CSSFile} | {ct_hooks, CTHs}</v>
+ <v>Time = TimeVal | TimeFunc</v>
+ <v>TimeVal = MilliSec | {seconds, integer()} | {minutes, integer()} | {hours, integer()}</v>
+ <v>TimeFunc = {Mod, Func, Args} | Fun</v>
+ <v>MilliSec = integer()</v>
+ <v>Mod = atom()</v>
+ <v>Func = atom()</v>
+ <v>Args = list()</v>
+ <v>Fun = fun()</v>
+ <v>Required = Key | {Key, SubKeys} | {Key, SubKey} | {Key, SubKey, SubKeys}</v>
+ <v>Key = atom()</v>
+ <v>SubKeys = SubKey | [SubKey]</v>
+ <v>SubKey = atom()</v>
+ <v>Name = atom()</v>
+ <v>UserData = term()</v>
+ <v>Conns = [atom()]</v>
+ <v>CSSFile = string()</v>
+ <v>CTHs = [CTHModule |</v>
+ <v>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{CTHModule, CTHInitArgs} |</v>
+ <v>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{CTHModule, CTHInitArgs, CTHPriority}]</v>
+ <v>CTHModule = atom()</v>
+ <v>CTHInitArgs = term()</v>
+ <v>CTHPriority = integer()</v>
+ </type>
+ <desc>
+
+ <p>OPTIONAL</p>
+
+ <p>The test suite information function. Returns a list of tagged
+ tuples specifying various properties related to the execution of
+ this test suite (common for all test cases in the suite).</p>
+
+ <p>Tag <c>timetrap</c> sets the maximum time that each
+ test case is allowed to execute (including
+ <seemfa marker="#Module:init_per_testcase/2"><c>Module:init_per_testcase/2</c></seemfa>
+ and
+ <seemfa marker="#Module:end_per_testcase/2"><c>Module:end_per_testcase/2</c></seemfa>).
+ If the timetrap time is exceeded, the test case fails with reason
+ <c>timetrap_timeout</c>. A <c>TimeFunc</c> function can be used to
+ set a new timetrap by returning a <c>TimeVal</c>. It can also be
+ used to trigger a timetrap time-out by, at some point, returning a
+ value other than a <c>TimeVal</c>. For details, see section
+ <seeguide marker="write_test_chapter#timetraps">Timetrap Time-Outs</seeguide>
+ in the User's Guide.</p>
+
+ <p>Tag <c>require</c> specifies configuration variables
+ required by test cases (or configuration functions)
+ in the suite. If the required configuration variables are not found
+ in any of the configuration files, all test cases are skipped.
+ For details about the <c>require</c> functionality, see funtion
+ <seemfa marker="ct#require/1"><c>ct:require/1,2</c></seemfa>.</p>
+
+ <p>With <c>userdata</c>, the user can
+ specify any test suite-related information, which can be
+ read by calling
+ <seemfa marker="ct#userdata/2"><c>ct:userdata/2</c></seemfa>.</p>
+
+ <p>Tag <c>ct_hooks</c> specifies the
+ <seeguide marker="ct_hooks_chapter">Common Test Hooks</seeguide>
+ to be run with this suite.</p>
+
+ <p>Other tuples than the ones defined are ignored.</p>
+
+ <p>For details about the test suite information function, see section
+ <seeguide marker="write_test_chapter#suite">Test
+ Suite Information Function</seeguide> in the User's Guide.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="">Module:init_per_suite(Config) -> NewConfig | {skip, Reason} |
+ {skip_and_save, Reason, SaveConfig}</name>
+ <fsummary>Test suite initializations.</fsummary>
+ <type>
+ <v>Config = NewConfig = SaveConfig = <seetype marker="#ct_config">ct_config()</seetype></v>
+ <v>Reason = term()</v>
+ </type>
+ <desc>
+
+ <p>OPTIONAL; if this function is defined, then <seemfa
+ marker="#Module:end_per_suite/1"><c>Module:end_per_suite/1</c></seemfa>
+ must also be defined.</p>
+
+ <p>This configuration function is called as the first function in the
+ suite. It typically contains initializations that are common for
+ all test cases in the suite, and that must only be done
+ once. Parameter <c>Config</c> is the configuration data
+ that can be modified. Whatever is returned from this
+ function is specified as <c>Config</c> to all configuration functions
+ and test cases in the suite.</p>
+
+ <p>If <c>{skip, Reason}</c>
+ is returned, all test cases in the suite are skipped
+ and <c>Reason</c> is printed in the overview log for the suite.</p>
+
+ <p>For information on <c>save_config</c> and <c>skip_and_save</c>,
+ see section
+ <seeguide marker="dependencies_chapter#save_config">Saving
+ Configuration Data</seeguide> in the User's Guide.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="">Module:end_per_suite(Config) -> term() |
+ {save_config, SaveConfig}</name>
+ <fsummary>Test suite finalization.</fsummary>
+ <type>
+ <v>Config = SaveConfig = <seetype marker="#ct_config">ct_config()</seetype></v>
+ </type>
+
+ <desc>
+ <p>OPTIONAL; if this function is defined, then <seemfa
+ marker="#Module:init_per_suite/1"><c>Module:init_per_suite/1</c></seemfa>
+ must also be defined.</p>
+
+ <p>This function is called as the last test case in the
+ suite. It is meant to be used for cleaning up after
+ <seemfa marker="#Module:init_per_suite/1"><c>Module:init_per_suite/1</c></seemfa>.</p>
+ <p>For information on <c>save_config</c>, see section
+ <seeguide marker="dependencies_chapter#save_config">Saving
+ Configuration Data</seeguide> in the User's Guide.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="OTP R15B">Module:group(GroupName) -> [ct_info()]</name>
+ <fsummary>Test case group information function (providing default data
+ for a test case group, that is, its test cases and
+ subgroups).</fsummary>
+ <type>
+ <v>GroupName = <seetype marker="#ct_groupname">ct_groupname()</seetype></v>
+ <v><seetype marker="#ct_info">ct_info()</seetype> = {timetrap, Time} | {require, Required} | {require, Name, Required} | {userdata, UserData} | {silent_connections, Conns} | {stylesheet, CSSFile} | {ct_hooks, CTHs}</v>
+ <v>Time = TimeVal | TimeFunc</v>
+ <v>TimeVal = MilliSec | {seconds, integer()} | {minutes, integer()} | {hours, integer()}</v>
+ <v>TimeFunc = {Mod, Func, Args} | Fun</v>
+ <v>MilliSec = integer()</v>
+ <v>Mod = atom()</v>
+ <v>Func = atom()</v>
+ <v>Args = list()</v>
+ <v>Fun = fun()</v>
+ <v>Required = Key | {Key, SubKeys} | {Key, SubKey} | {Key, SubKey, SubKeys}</v>
+ <v>Key = atom()</v>
+ <v>SubKeys = SubKey | [SubKey]</v>
+ <v>SubKey = atom()</v>
+ <v>Name = atom()</v>
+ <v>UserData = term()</v>
+ <v>Conns = [atom()]</v>
+ <v>CSSFile = string()</v>
+ <v>CTHs = [CTHModule |</v>
+ <v>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{CTHModule, CTHInitArgs} |</v>
+ <v>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{CTHModule, CTHInitArgs, CTHPriority}]</v>
+ <v>CTHModule = atom()</v>
+ <v>CTHInitArgs = term()</v>
+ <v>CTHPriority = integer()</v>
+ </type>
+ <desc>
+
+ <p>OPTIONAL</p>
+
+ <p>The test case group information function. It is supposed to
+ return a list of tagged tuples that specify various properties
+ related to the execution of a test case group (that is, its test
+ cases and subgroups). Properties set by
+ <seemfa marker="#Module:group/1"><c>Module:group/1</c></seemfa> override
+ properties with the same key that have been set previously by
+ <seemfa marker="#Module:suite/0"><c>Module:suite/0</c></seemfa>.</p>
+
+ <p>Tag <c>timetrap</c> sets the maximum time that each
+ test case is allowed to execute (including
+ <seemfa marker="#Module:init_per_testcase/2"><c>Module:init_per_testcase/2</c></seemfa>
+ and
+ <seemfa marker="#Module:end_per_testcase/2"><c>Module:end_per_testcase/2</c></seemfa>).
+ If the timetrap time is
+ exceeded, the test case fails with reason
+ <c>timetrap_timeout</c>. A <c>TimeFunc</c> function can be used to
+ set a new timetrap by returning a <c>TimeVal</c>. It can also be
+ used to trigger a timetrap time-out by, at some point, returning a
+ value other than a <c>TimeVal</c>. For details, see section
+ <seeguide marker="write_test_chapter#timetraps">Timetrap
+ Time-Outs</seeguide> in the User's Guide.</p>
+
+ <p>Tag <c>require</c> specifies configuration variables
+ required by test cases (or configuration functions)
+ in the suite. If the required configuration variables are not found
+ in any of the configuration files, all test cases in this group are
+ skipped. For details about the <c>require</c> functionality, see
+ function
+ <seemfa marker="ct#require/1"><c>ct:require/1,2</c></seemfa>.</p>
+
+ <p>With <c>userdata</c>, the user can
+ specify any test case group related information that can be
+ read by calling
+ <seemfa marker="ct#userdata/2"><c>ct:userdata/2</c></seemfa>.</p>
+
+ <p>Tag <c>ct_hooks</c> specifies the
+ <seeguide marker="ct_hooks_chapter">Common Test Hooks</seeguide>
+ to be run with this suite.</p>
+
+ <p>Other tuples than the ones defined are ignored.</p>
+
+ <p>For details about the test case group information function,
+ see section <seeguide marker="write_test_chapter#group_info">Group
+ Information Function</seeguide> in the User's Guide.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="">Module:init_per_group(GroupName, Config) -> NewConfig |
+ {skip, Reason}</name>
+ <fsummary>Test case group initializations.</fsummary>
+ <type>
+ <v>GroupName = <seetype marker="#ct_groupname">ct_groupname()</seetype></v>
+ <v>Config = NewConfig = <seetype marker="#ct_config">ct_config()</seetype></v>
+ <v>Reason = term()</v>
+ </type>
+ <desc>
+
+ <p>OPTIONAL; if this function is defined, then <seemfa
+ marker="#Module:end_per_group/2"><c>Module:end_per_group/2</c></seemfa>
+ must also be defined.</p>
+
+ <p>This configuration function is called before execution of a
+ test case group. It typically contains initializations that are
+ common for all test cases and subgroups in the group, and that
+ must only be performed once. <c>GroupName</c> is the name of the
+ group, as specified in the group definition (see
+ <seemfa marker="#Module:groups/0"><c>Module:groups/0</c></seemfa>).
+ Parameter <c>Config</c> is the configuration data that can be
+ modified.
+ The return value of this function is given as <c>Config</c>
+ to all test cases and subgroups in the group.</p>
+
+ <p>If <c>{skip, Reason}</c>
+ is returned, all test cases in the group are skipped and
+ <c>Reason</c> is printed in the overview log for the group.</p>
+
+ <p>For information about test case groups, see section
+ <seeguide marker="write_test_chapter#test_case_groups">Test Case
+ Groups</seeguide> in the User's Guide.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="">Module:end_per_group(GroupName, Config) -> term() |
+ {return_group_result, Status}</name>
+ <fsummary>Test case group finalization.</fsummary>
+ <type>
+ <v>GroupName = <seetype marker="#ct_groupname">ct_groupname()</seetype></v>
+ <v>Config = <seetype marker="#ct_config">ct_config()</seetype></v>
+ <v>Status = <seetype marker="#ct_status">ct_status()</seetype></v>
+ </type>
+
+ <desc>
+ <p>OPTIONAL; if this function is defined, then <seemfa
+ marker="#Module:init_per_group/2"><c>Module:init_per_group/2</c></seemfa>
+ must also be defined.</p>
+
+ <p>This function is called after the execution of a test case group
+ is finished. It is meant to be used for cleaning up after
+ <seemfa marker="#Module:init_per_group/2"><c>Module:init_per_group/2</c></seemfa>.
+ A status value for a nested subgroup can be returned with
+ <c>{return_group_result, Status}</c>. The status can be retrieved in
+ <seemfa marker="#Module:end_per_group/2"><c>Module:end_per_group/2</c></seemfa>
+ for the group on the level above. The status is also used by
+ <c>Common Test</c> for deciding if execution of a group is to
+ proceed if property <c>sequence</c> or <c>repeat_until_*</c>
+ is set.</p>
+
+ <p>For details about test case groups, see section
+ <seeguide marker="write_test_chapter#test_case_groups">Test Case
+ Groups</seeguide> in the User's Guide.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="">Module:init_per_testcase(TestCase, Config) -> NewConfig | {fail, Reason} | {skip, Reason}</name>
+ <fsummary>Test case initializations.</fsummary>
+ <type>
+ <v>TestCase = <seetype marker="#ct_testname">ct_testname()</seetype></v>
+ <v>Config = NewConfig = <seetype marker="#ct_config">ct_config()</seetype></v>
+ <v>Reason = term()</v>
+ </type>
+ <desc>
+
+ <p>OPTIONAL; if this function is defined,
+ then <seemfa marker="#Module:end_per_testcase/2">
+ <c>Module:end_per_testcase/2</c></seemfa> must also be
+ defined.</p>
+
+ <p>This function is called before each test case. Argument
+ <c>TestCase</c> is the test case name, and
+ <c>Config</c> (list of key-value tuples) is the configuration
+ data that can be modified. The <c>NewConfig</c> list returned
+ from this function is given as <c>Config</c> to the test case.
+ If <c>{fail, Reason}</c> is returned, the test case is
+ marked as failed without being executed.</p>
+
+ <p>If <c>{skip, Reason}</c> is returned, the test case is skipped
+ and <c>Reason</c> is printed in the overview log for the suite.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="">Module:end_per_testcase(TestCase, Config) -> term() | {fail, Reason} | {save_config, SaveConfig}</name>
+ <fsummary>Test case finalization.</fsummary>
+ <type>
+ <v>TestCase = <seetype marker="#ct_testname">ct_testname()</seetype></v>
+ <v>Config = SaveConfig = <seetype marker="#ct_config">ct_config()</seetype></v>
+ <v>Reason = term()</v>
+ </type>
+ <desc>
+
+ <p>OPTIONAL; if this function is defined,
+ then <seemfa marker="#Module:init_per_testcase/2">
+ <c>Module:init_per_testcase/2</c></seemfa> must also be
+ defined.</p>
+
+ <p>This function is called after each test case, and can be used
+ to clean up after
+ <seemfa marker="#Module:init_per_testcase/2"><c>Module:init_per_testcase/2</c></seemfa>
+ and the test case. Any return value (besides <c>{fail, Reason}</c>
+ and <c>{save_config, SaveConfig}</c>) is ignored. By returning
+ <c>{fail, Reason}</c>, <c>TestCase</c> is marked as faulty (even
+ though it was successful in the sense that it returned
+ a value instead of terminating).</p>
+
+ <p>For information on <c>save_config</c>, see section
+ <seeguide marker="dependencies_chapter#save_config">Saving
+ Configuration Data</seeguide> in the User's Guide.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="OTP R14B">Module:Testcase() -> [ct_info()] </name>
+ <fsummary>Test case information function.</fsummary>
+ <type>
+ <v><seetype marker="#ct_info">ct_info()</seetype> = {timetrap, Time} | {require, Required} | {require, Name, Required} | {userdata, UserData} | {silent_connections, Conns} | {stylesheet, CSSFile} | {ct_hooks, CTHs}</v>
+ <v>Time = TimeVal | TimeFunc</v>
+ <v>TimeVal = MilliSec | {seconds, integer()} | {minutes, integer()} | {hours, integer()}</v>
+ <v>TimeFunc = {Mod, Func, Args} | Fun</v>
+ <v>MilliSec = integer()</v>
+ <v>Mod = atom()</v>
+ <v>Func = atom()</v>
+ <v>Args = list()</v>
+ <v>Fun = fun()</v>
+ <v>Required = Key | {Key, SubKeys} | {Key, SubKey} | {Key, SubKey, SubKeys}</v>
+ <v>Key = atom()</v>
+ <v>SubKeys = SubKey | [SubKey]</v>
+ <v>SubKey = atom()</v>
+ <v>Name = atom()</v>
+ <v>UserData = term()</v>
+ <v>Conns = [atom()]</v>
+ <v>CSSFile = string()</v>
+ <v>CTHs = [CTHModule |</v>
+ <v>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{CTHModule, CTHInitArgs} |</v>
+ <v>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{CTHModule, CTHInitArgs, CTHPriority}]</v>
+ <v>CTHModule = atom()</v>
+ <v>CTHInitArgs = term()</v>
+ <v>CTHPriority = integer()</v>
+ </type>
+
+ <desc>
+
+ <p>OPTIONAL</p>
+
+ <p>The test case information function. It is supposed to
+ return a list of tagged tuples that specify various properties
+ related to the execution of this particular test case.
+ Properties set by
+ <seemfa marker="#Module:Testcase/0"><c>Module:Testcase/0</c></seemfa>
+ override properties set previously for the test case by
+ <seemfa marker="#Module:group/1"><c>Module:group/1</c></seemfa> or
+ <seemfa marker="#Module:suite/0"><c>Module:suite/0</c></seemfa>.</p>
+
+ <p>Tag <c>timetrap</c> sets the maximum time that the
+ test case is allowed to execute. If the timetrap time is
+ exceeded, the test case fails with reason <c>timetrap_timeout</c>.
+ <seemfa marker="#Module:init_per_testcase/2"><c>Module:init_per_testcase/2</c></seemfa>
+ and
+ <seemfa marker="#Module:end_per_testcase/2"><c>Module:end_per_testcase/2</c></seemfa>
+ are included in the timetrap time.
+ A <c>TimeFunc</c> function can be used to
+ set a new timetrap by returning a <c>TimeVal</c>. It can also be
+ used to trigger a timetrap time-out by, at some point, returning a
+ value other than a <c>TimeVal</c>. For details, see section
+ <seeguide marker="write_test_chapter#timetraps">Timetrap
+ Time-Outs</seeguide> in the User's Guide.</p>
+
+ <p>Tag <c>require</c> specifies configuration variables
+ that are required by the test case (or <c>init_per_testcase/2</c>
+ or <c>end_per_testcase/2</c>).
+ If the required configuration variables are not found in any of the
+ configuration files, the test case is skipped. For details about
+ the <c>require</c> functionality, see function
+ <seemfa marker="ct#require/1"><c>ct:require/1,2</c></seemfa>.</p>
+
+ <p>If <c>timetrap</c> or <c>require</c> is not set, the
+ default values specified by
+ <seemfa marker="#Module:suite/0"><c>Module:suite/0</c></seemfa> (or
+ <seemfa marker="#Module:group/1"><c>Module:group/1</c></seemfa>) are used.</p>
+
+ <p>With <c>userdata</c>, the user can specify any test case-related
+ information that can be read by calling
+ <seemfa marker="ct#userdata/3"><c>ct:userdata/3</c></seemfa>.</p>
+
+ <p>Other tuples than the ones defined are ignored.</p>
+
+ <p>For details about the test case information function, see section
+ <seeguide marker="write_test_chapter#info_function">Test
+ Case Information Function</seeguide> in the User's Guide.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="OTP R14B">Module:Testcase(Config) -> term() | {skip, Reason} | {comment, Comment} | {save_config, SaveConfig} | {skip_and_save, Reason, SaveConfig} | exit()</name>
+ <fsummary>A test case.</fsummary>
+ <type>
+ <v>Config = SaveConfig = <seetype marker="#ct_config">ct_config()</seetype></v>
+ <v>Reason = term()</v>
+ <v>Comment = string()</v>
+ </type>
+
+ <desc>
+ <p>MANDATORY</p>
+
+ <p>The implementation of a test case. Call the functions to test and
+ check the result. If something fails, ensure the
+ function causes a runtime error or call
+ <seemfa marker="ct#fail/1"><c>ct:fail/1,2</c></seemfa>
+ (which also causes the test case process to terminate).</p>
+
+ <p>Elements from the <c>Config</c> list can, for example, be read
+ with <c>proplists:get_value/2</c> in STDLIB
+ (or the macro <c>?config</c> defined in <c>ct.hrl</c>).</p>
+
+ <p>If you decide not to run the test case after all, return
+ <c>{skip, Reason}</c>. <c>Reason</c> is then
+ printed in field <c>Comment</c> on the HTML result page.</p>
+
+ <p>To print some information in field <c>Comment</c> on the HTML
+ result page, return <c>{comment, Comment}</c>.</p>
+
+ <p>If the function returns anything else, the test case is
+ considered successful. The return value always gets printed
+ in the test case log file.</p>
+
+ <p>For details about test case implementation, see section
+ <seeguide marker="write_test_chapter#test_cases">Test Cases</seeguide>
+ in the User's Guide.</p>
+
+ <p>For information on <c>save_config</c> and <c>skip_and_save</c>,
+ see section
+ <seeguide marker="dependencies_chapter#save_config">Saving
+ Configuration Data</seeguide> in the User's Guide.</p>
+ </desc>
+ </func>
+
+ </funcs>
+
+</erlref>
diff --git a/lib/common_test/doc/src/dependencies_chapter.xml b/lib/common_test/doc/src/dependencies_chapter.xml
index 53a7673489..0a0f4c562f 100644
--- a/lib/common_test/doc/src/dependencies_chapter.xml
+++ b/lib/common_test/doc/src/dependencies_chapter.xml
@@ -87,8 +87,8 @@
<p>To avoid this, we can consider starting and stopping the server for every test.
We can thus implement the start and stop action as common functions to be
called from
- <seemfa marker="common_test#Module:init_per_testcase/2"><c>init_per_testcase</c></seemfa> and
- <seemfa marker="common_test#Module:end_per_testcase/2"><c>end_per_testcase</c></seemfa>.
+ <seemfa marker="ct_suite#Module:init_per_testcase/2"><c>init_per_testcase</c></seemfa> and
+ <seemfa marker="ct_suite#Module:end_per_testcase/2"><c>end_per_testcase</c></seemfa>.
(Remember to test the start and stop functionality separately.)
The configuration can also be implemented as a common function, maybe grouped
with the start function. Finally, the testing of connecting and disconnecting a
@@ -194,9 +194,9 @@
<p>To pass data from one test suite to another, the same mechanism is used. The data
is to be saved by finction
- <seemfa marker="common_test#Module:end_per_suite/1"><c>end_per_suite</c></seemfa>
+ <seemfa marker="ct_suite#Module:end_per_suite/1"><c>end_per_suite</c></seemfa>
and read by function
- <seemfa marker="common_test#Module:init_per_suite/1"><c>init_per_suite</c></seemfa>
+ <seemfa marker="ct_suite#Module:init_per_suite/1"><c>init_per_suite</c></seemfa>
in the suite that follows. When passing data between suites, <c>Saver</c> carries the
name of the test suite.</p>
@@ -306,7 +306,7 @@
any property, that is, they are not required to also be sequences. If you want the
status of the subgroup to affect the sequence on the level above, return
<c>{return_group_result,Status}</c> from
- <seemfa marker="common_test#Module:end_per_group/2"><c>end_per_group/2</c></seemfa>,
+ <seemfa marker="ct_suite#Module:end_per_group/2"><c>end_per_group/2</c></seemfa>,
as described in section
<seeguide marker="write_test_chapter#repeated_groups">Repeated Groups</seeguide>
in Writing Test Suites.
diff --git a/lib/common_test/doc/src/notes.xml b/lib/common_test/doc/src/notes.xml
index 9545d8352e..c4f5c7bb11 100644
--- a/lib/common_test/doc/src/notes.xml
+++ b/lib/common_test/doc/src/notes.xml
@@ -33,6 +33,21 @@
<file>notes.xml</file>
</header>
+<section><title>Common_Test 1.19.1</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Add behaviour for test suites</p>
+ <p>
+ Own Id: OTP-17070</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Common_Test 1.19</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/common_test/doc/src/ref_man.xml b/lib/common_test/doc/src/ref_man.xml
index e916fc7cec..b8184ed7f8 100644
--- a/lib/common_test/doc/src/ref_man.xml
+++ b/lib/common_test/doc/src/ref_man.xml
@@ -48,6 +48,7 @@
<xi:include href="ct_hooks.xml"/>
<xi:include href="ct_property_test.xml"/>
<xi:include href="ct_testspec.xml"/>
+ <xi:include href="ct_suite.xml"/>
</application>
diff --git a/lib/common_test/doc/src/specs.xml b/lib/common_test/doc/src/specs.xml
index 7e40e8351d..def8f761a5 100644
--- a/lib/common_test/doc/src/specs.xml
+++ b/lib/common_test/doc/src/specs.xml
@@ -2,4 +2,5 @@
<specs xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="../specs/specs_ct_netconfc.xml"/>
<xi:include href="../specs/specs_ct.xml"/>
+ <xi:include href="../specs/specs_ct_suite.xml"/>
</specs>
diff --git a/lib/common_test/doc/src/test_structure_chapter.xml b/lib/common_test/doc/src/test_structure_chapter.xml
index 42eb3d6e23..e041376d7f 100644
--- a/lib/common_test/doc/src/test_structure_chapter.xml
+++ b/lib/common_test/doc/src/test_structure_chapter.xml
@@ -60,8 +60,8 @@
</item>
<item>Returning <c>{skip,Reason}</c> from function
- <seemfa marker="common_test#Module:init_per_testcase/2"><c>init_per_testcase/2</c></seemfa> or
- <seemfa marker="common_test#Module:init_per_suite/1"><c>init_per_suite/1</c></seemfa>.</item>
+ <seemfa marker="ct_suite#Module:init_per_testcase/2"><c>init_per_testcase/2</c></seemfa> or
+ <seemfa marker="ct_suite#Module:init_per_suite/1"><c>init_per_suite/1</c></seemfa>.</item>
<item>Returning <c>{skip,Reason}</c> from the execution clause
of the test case. The execution clause is called, so the author
diff --git a/lib/common_test/doc/src/write_test_chapter.xml b/lib/common_test/doc/src/write_test_chapter.xml
index 3c3790cfc5..661ba04fe8 100644
--- a/lib/common_test/doc/src/write_test_chapter.xml
+++ b/lib/common_test/doc/src/write_test_chapter.xml
@@ -67,7 +67,7 @@
</p>
<p>Each test suite module must export function
- <seemfa marker="common_test#Module:all/0"><c>all/0</c></seemfa>,
+ <seemfa marker="ct_suite#Module:all/0"><c>all/0</c></seemfa>,
which returns the list of all test case groups and test cases
to be executed in that module.
</p>
@@ -83,8 +83,8 @@
<title>Init and End per Suite</title>
<p>Each test suite module can contain the optional configuration functions
- <seemfa marker="common_test#Module:init_per_suite/1"><c>init_per_suite/1</c></seemfa>
- and <seemfa marker="common_test#Module:end_per_suite/1"><c>end_per_suite/1</c></seemfa>.
+ <seemfa marker="ct_suite#Module:init_per_suite/1"><c>init_per_suite/1</c></seemfa>
+ and <seemfa marker="ct_suite#Module:end_per_suite/1"><c>end_per_suite/1</c></seemfa>.
If the init function is defined, so must the end function be.
</p>
@@ -138,8 +138,8 @@
<title>Init and End per Test Case</title>
<p>Each test suite module can contain the optional configuration functions
- <seemfa marker="common_test#Module:init_per_testcase/2"><c>init_per_testcase/2</c></seemfa>
- and <seemfa marker="common_test#Module:end_per_testcase/2"><c>end_per_testcase/2</c></seemfa>.
+ <seemfa marker="ct_suite#Module:init_per_testcase/2"><c>init_per_testcase/2</c></seemfa>
+ and <seemfa marker="ct_suite#Module:end_per_testcase/2"><c>end_per_testcase/2</c></seemfa>.
If the init function is defined, so must the end function be.</p>
<p>If <c>init_per_testcase</c> exists, it is called before each
@@ -378,7 +378,7 @@
<p>If <c>timetrap</c> or <c>require</c>, or both, is not set specifically for
a particular test case, default values specified by function
- <seemfa marker="common_test#Module:suite/0"><c>suite/0</c></seemfa>
+ <seemfa marker="ct_suite#Module:suite/0"><c>suite/0</c></seemfa>
are used.
</p>
@@ -404,7 +404,7 @@
<marker id="suite"></marker>
<title>Test Suite Information Function</title>
- <p>Function <seemfa marker="common_test#Module:suite/0"><c>suite/0</c></seemfa>
+ <p>Function <seemfa marker="ct_suite#Module:suite/0"><c>suite/0</c></seemfa>
can, for example, be used in a test suite module to set a default
<c>timetrap</c> value and to <c>require</c> external configuration data.
If a test case, or a group information function also specifies any of the information tags, it
@@ -445,7 +445,7 @@
<p>A test case group is a set of test cases sharing configuration
functions and execution properties. Test case groups are defined by
function
- <seemfa marker="common_test#Module:groups/0"><c>groups/0</c></seemfa>
+ <seemfa marker="ct_suite#Module:groups/0"><c>groups/0</c></seemfa>
according to the following syntax:</p>
<pre>
groups() -> GroupDefs
@@ -561,12 +561,12 @@
execution is immediately stopped and the remaining cases are skipped.</p>
<p>Before execution of a group begins, the configuration function
- <seemfa marker="common_test#Module:init_per_group/2"><c>init_per_group(GroupName, Config)</c></seemfa>
+ <seemfa marker="ct_suite#Module:init_per_group/2"><c>init_per_group(GroupName, Config)</c></seemfa>
is called. The list of tuples returned from this function is passed to the
test cases in the usual manner by argument <c>Config</c>.
<c>init_per_group/2</c> is meant to be used for initializations common
for the test cases in the group. After execution of the group is finished, function
- <seemfa marker="common_test#Module:end_per_group/2"><c>end_per_group(GroupName, Config)</c></seemfa>
+ <seemfa marker="ct_suite#Module:end_per_group/2"><c>end_per_group(GroupName, Config)</c></seemfa>
is called. This function is meant to be used for cleaning up after
<c>init_per_group/2</c>. If the init function is defined, so must the end function be.</p>
@@ -1163,7 +1163,7 @@ ct:pal(?ERROR, "Error report: ~p", [Error])</pre>
environment as possible, so that subsequent test cases
do not crash because of their execution order.
The function
- <seemfa marker="common_test#Module:end_per_testcase/2"><c>end_per_testcase</c></seemfa>
+ <seemfa marker="ct_suite#Module:end_per_testcase/2"><c>end_per_testcase</c></seemfa>
is suitable for this.
</p>
</item>
diff --git a/lib/common_test/src/Makefile b/lib/common_test/src/Makefile
index a20de14e0e..7d7b5ed203 100644
--- a/lib/common_test/src/Makefile
+++ b/lib/common_test/src/Makefile
@@ -40,6 +40,9 @@ RELSYSDIR = $(RELEASE_PATH)/lib/common_test-$(VSN)
# Target Specs
# ----------------------------------------------------
+BEHAVIOUR_MODULES= \
+ ct_suite
+
MODULES= \
ct \
ct_logs \
@@ -88,9 +91,13 @@ MODULES= \
TARGET_MODULES= $(MODULES:%=$(EBIN)/%)
-BEAM_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR))
+BEAM_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) \
+ $(BEHAVIOUR_MODULES:%=$(EBIN)/%.$(EMULATOR))
+
+ERL_FILES= \
+ $(MODULES:=.erl) \
+ $(BEHAVIOUR_MODULES:%=%.erl)
-ERL_FILES= $(MODULES:=.erl)
HRL_FILES = \
ct_util.hrl \
ct_netconfc.hrl
diff --git a/lib/common_test/src/common_test.app.src b/lib/common_test/src/common_test.app.src
index 07b35b7180..8aa15efa7e 100644
--- a/lib/common_test/src/common_test.app.src
+++ b/lib/common_test/src/common_test.app.src
@@ -63,7 +63,8 @@
test_server_gl,
test_server_io,
test_server_node,
- test_server_sup
+ test_server_sup,
+ ct_suite
]},
{registered, [ct_logs,
ct_util_server,
diff --git a/lib/common_test/src/ct_suite.erl b/lib/common_test/src/ct_suite.erl
new file mode 100644
index 0000000000..a2d23e15ef
--- /dev/null
+++ b/lib/common_test/src/ct_suite.erl
@@ -0,0 +1,130 @@
+-module(ct_suite).
+
+%%------------------------------------------------------------------
+%% Test Suite Behaviour
+%% ------------------------------------------------------------------
+-export_type([ct_testname/0,
+ ct_groupname/0,
+ ct_config/0,
+ ct_status/0,
+ ct_group_def/0,
+ ct_test_def/0,
+ ct_info/0
+ ]).
+
+-type ct_testname() :: atom().
+-type ct_groupname() :: atom().
+-type ct_config() :: [{Key :: atom(), Value :: term()}].
+-type ct_status() :: ok |
+ skipped |
+ failed.
+-type ct_group_props() :: [
+ parallel |
+ sequence |
+ shuffle |
+ {shuffle, Seed :: {integer(), integer(), integer()}} |
+ {ct_group_repeat_type(), ct_test_repeat()}
+ ].
+-type ct_group_props_ref() ::
+ ct_group_props() |
+ default.
+-type ct_group_repeat_type() :: repeat |
+ repeat_until_all_ok |
+ repeat_until_all_fail |
+ repeat_until_any_ok |
+ repeat_until_any_fail.
+-type ct_test_repeat() :: integer() |
+ forever.
+-type ct_group_def() :: {ct_groupname(), ct_group_props(), [
+ ct_testname() |
+ ct_group_def() |
+ {group, ct_groupname()} |
+ ct_testcase_ref()
+ ]}.
+-type ct_subgroups_def() :: {ct_groupname(), ct_group_props_ref()} |
+ {ct_groupname(), ct_group_props_ref(), ct_subgroups_def()}.
+-type ct_group_ref() :: {group, ct_groupname()} |
+ {group, ct_groupname(), ct_group_props_ref()} |
+ {group, ct_groupname(), ct_group_props_ref(), ct_subgroups_def()}.
+-type ct_testcase_ref() :: {testcase, ct_testname(), ct_testcase_repeat_prop()}.
+-type ct_testcase_repeat_prop() :: {repeat, ct_test_repeat()} |
+ {repeat_until_ok, ct_test_repeat()} |
+ {repeat_until_fail, ct_test_repeat()}.
+-type ct_info() :: {timetrap, ct_info_timetrap()} |
+ {require, ct_info_required()} |
+ {require, Name :: atom(), ct_info_required()} |
+ {userdata, UserData :: term()} |
+ {silent_connections, Conns :: [atom()]} |
+ {stylesheet, CSSFile :: string()} |
+ {ct_hooks, CTHs :: ct_hooks()}.
+-type ct_info_timetrap() :: MilliSec :: integer() |
+ {seconds, integer()} |
+ {minutes, integer()} |
+ {hours, integer()} |
+ {Mod :: atom(), Func :: atom(), Args :: list()} |
+ ct_info_timetrap_fun().
+-type ct_info_timetrap_fun() :: fun().
+-type ct_info_required() :: Key :: atom() |
+ {Key :: atom(), SubKeys :: ct_info_required_subkeys()} |
+ {Key :: atom(), SubKey :: atom()} |
+ {Key :: atom(), SubKey :: atom(), SubKeys :: ct_info_required_subkeys()}.
+-type ct_info_required_subkeys() :: SubKey :: atom() |
+ [SubKey :: atom()].
+-type ct_hooks() :: [
+ CTHModule :: atom() |
+ {CTHModule :: atom(), CTHInitArgs :: term()} |
+ {CTHModule :: atom(), CTHInitArgs :: term(), CTHPriority :: integer()}
+ ].
+-type ct_test_def() :: ct_testname() | ct_group_ref() | ct_testcase_ref().
+
+-callback all() ->
+ [TestDef :: ct_test_def()] |
+ {skip, Reason :: term()}.
+
+-callback groups() ->
+ [GroupDef :: ct_group_def()].
+
+-callback suite() ->
+ [Info :: ct_info()].
+
+-callback init_per_suite(Config :: ct_config()) ->
+ NewConfig :: ct_config() |
+ {skip, Reason :: term()} |
+ {skip_and_save, Reason :: term(), SaveConfig :: ct_config()}.
+
+-callback end_per_suite(Config :: ct_config()) ->
+ term() |
+ {save_config, SaveConfig :: ct_config()}.
+
+-callback group(GroupName :: ct_groupname()) ->
+ [Info :: ct_info()].
+
+-callback init_per_group(GroupName :: ct_groupname(), Config :: ct_config()) ->
+ NewConfig :: ct_config() |
+ {skip, Reason :: term()}.
+
+-callback end_per_group(GroupName :: ct_groupname(), Config :: ct_config()) ->
+ term() |
+ {return_group_result, Status :: ct_status()}.
+
+-callback init_per_testcase(TestCase :: ct_testname(), Config :: ct_config()) ->
+ NewConfig :: ct_config() |
+ {fail, Reason :: term()} |
+ {skip, Reason :: term()}.
+
+-callback end_per_testcase(TestCase :: ct_testname(), Config :: ct_config()) ->
+ term() |
+ {fail, Reason :: term()} |
+ {save_config, SaveConfig :: ct_config()}.
+
+%% only all/0 is mandatory
+-optional_callbacks([groups/0,
+ suite/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ group/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2
+ ]).
diff --git a/lib/common_test/vsn.mk b/lib/common_test/vsn.mk
index b5fa287e1f..1994d75518 100644
--- a/lib/common_test/vsn.mk
+++ b/lib/common_test/vsn.mk
@@ -1 +1 @@
-COMMON_TEST_VSN = 1.19
+COMMON_TEST_VSN = 1.19.1
diff --git a/lib/compiler/doc/src/notes.xml b/lib/compiler/doc/src/notes.xml
index 0a82852bdd..b9978401a5 100644
--- a/lib/compiler/doc/src/notes.xml
+++ b/lib/compiler/doc/src/notes.xml
@@ -32,6 +32,51 @@
<p>This document describes the changes made to the Compiler
application.</p>
+<section><title>Compiler 7.6.6</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Several minor compiler bugs have been fixed:</p>
+ <p>Constructing a binary with a list as a size of a
+ binary segment could generate a BEAM file that could not
+ be loaded.</p>
+ <p>When matching a binary segment of type <c>float</c>
+ and ignoring the matched out value, the match would
+ always succeed, even if the size was invalid or the value
+ of the float was NaN or some other non-numeric float
+ value.</p>
+ <p>Attempting to construct an invalid external fun (e.g.
+ <c>fun m:f:bad</c>) is supposed to raise a
+ '<c>badarg</c>' exception, but if the value was never
+ used, no exception would be raised.</p>
+ <p>
+ Own Id: OTP-16932</p>
+ </item>
+ <item>
+ <p>Fixed multiple bugs in the validator that could cause
+ it to reject valid code.</p>
+ <p>
+ Own Id: OTP-17039 Aux Id: ERL-1426 </p>
+ </item>
+ <item>
+ <p>The compiler could crash when a binary comprehension
+ had a generator that depended on another generator.</p>
+ <p>
+ Own Id: OTP-17045 Aux Id: ERL-1427 </p>
+ </item>
+ <item>
+ <p>Fixed a bug in the type optimization pass that could
+ yield incorrect values or cause the wrong clauses to be
+ executed.</p>
+ <p>
+ Own Id: OTP-17072 Aux Id: ERL-1440 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Compiler 7.6.5</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -242,6 +287,21 @@
</section>
+<section><title>Compiler 7.5.4.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Fixed a bug in the validator that could cause it to
+ reject valid code</p>
+ <p>
+ Own Id: OTP-17039 Aux Id: ERL-1426 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Compiler 7.5.4.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/compiler/src/beam_ssa_opt.erl b/lib/compiler/src/beam_ssa_opt.erl
index d77ad0cddf..787408a894 100644
--- a/lib/compiler/src/beam_ssa_opt.erl
+++ b/lib/compiler/src/beam_ssa_opt.erl
@@ -1515,7 +1515,9 @@ bsm_skip_is([I0|Is], Extracted) ->
#b_set{op=bs_match,
dst=Ctx,
args=[#b_literal{val=T}=Type,PrevCtx|Args0]}
- when T =/= string, T =/= skip ->
+ when T =/= float, T =/= string, T =/= skip ->
+ %% Note that it is never safe to skip matching
+ %% of floats, even if the size is known to be correct.
I = case cerl_sets:is_element(Ctx, Extracted) of
true ->
I0;
diff --git a/lib/compiler/src/beam_ssa_pre_codegen.erl b/lib/compiler/src/beam_ssa_pre_codegen.erl
index 9a52399b3b..efab700dbb 100644
--- a/lib/compiler/src/beam_ssa_pre_codegen.erl
+++ b/lib/compiler/src/beam_ssa_pre_codegen.erl
@@ -921,6 +921,12 @@ sanitize_instr({bif,Bif}, [#b_literal{val=Lit1},#b_literal{val=Lit2}], _I) ->
error:_ ->
ok
end;
+sanitize_instr(bs_match, Args, I) ->
+ %% Matching of floats are never changed to a bs_skip even when the
+ %% value is never used, because the match can always fail (for example,
+ %% if it is a NaN).
+ [#b_literal{val=float}|_] = Args, %Assertion.
+ {ok,I#b_set{op=bs_get}};
sanitize_instr(get_hd, [#b_literal{val=[Hd|_]}], _I) ->
{value,Hd};
sanitize_instr(get_tl, [#b_literal{val=[_|Tl]}], _I) ->
@@ -944,10 +950,12 @@ sanitize_instr(is_tagged_tuple, [#b_literal{val=Tuple},
true ->
{value,false}
end;
-sanitize_instr(bs_add, [_,#b_literal{val=Sz},_|_], I0) ->
- if
- is_integer(Sz), Sz >= 0 -> ok;
- true -> {ok,sanitize_badarg(I0)}
+sanitize_instr(bs_add, [Arg1,Arg2,_|_], I0) ->
+ case all(fun(#b_literal{val=Size}) -> is_integer(Size) andalso Size >= 0;
+ (#b_var{}) -> true
+ end, [Arg1,Arg2]) of
+ true -> ok;
+ false -> {ok,sanitize_badarg(I0)}
end;
sanitize_instr(bs_init, [#b_literal{val=new},#b_literal{val=Sz}|_], I0) ->
if
diff --git a/lib/compiler/src/beam_ssa_type.erl b/lib/compiler/src/beam_ssa_type.erl
index d40e7f3fc0..304c74c2f3 100644
--- a/lib/compiler/src/beam_ssa_type.erl
+++ b/lib/compiler/src/beam_ssa_type.erl
@@ -32,7 +32,7 @@
-include("beam_ssa_opt.hrl").
-include("beam_types.hrl").
--import(lists, [any/2,duplicate/2,foldl/3,member/2,
+-import(lists, [all/2,any/2,duplicate/2,foldl/3,member/2,
keyfind/3,reverse/1,split/2,zip/2]).
%% The maximum number of #b_ret{} terminators a function can have before
@@ -1013,13 +1013,13 @@ will_succeed_1(#b_set{op=put_tuple}, _Src, _Ts, _Sub) ->
%% Remove the success branch from binary operations with invalid
%% sizes. That will remove subsequent bs_put and bs_match instructions,
%% which are probably not loadable.
-will_succeed_1(#b_set{op=bs_add,args=[_,#b_literal{val=Size},_]},
+will_succeed_1(#b_set{op=bs_add,args=[Arg1,Arg2,_]},
_Src, _Ts, _Sub) ->
- if
- is_integer(Size), Size >= 0 ->
- maybe;
- true ->
- no
+ case all(fun(#b_literal{val=Size}) -> is_integer(Size) andalso Size >= 0;
+ (#b_var{}) -> true
+ end, [Arg1,Arg2]) of
+ true -> maybe;
+ false -> no
end;
will_succeed_1(#b_set{op=bs_init,
args=[#b_literal{val=new},#b_literal{val=Size},_Unit]},
@@ -1947,10 +1947,13 @@ infer_type({bif,'=:='}, [#b_var{}=LHS,#b_var{}=RHS], Ts, _Ds) ->
%%
%% However, it is safe to subtract a type inferred from '=:=' if
%% it is single-valued, e.g. if it is [] or the atom 'true'.
- NegTypes = case beam_types:is_singleton_type(Type) of
- true -> PosTypes;
- false -> []
- end,
+ %%
+ %% Note that we subtract the left-hand type from the right-hand
+ %% value and vice versa. We must not subtract the meet of the two
+ %% as it may be too specific. See beam_type_SUITE:type_subtraction/1
+ %% for details.
+ NegTypes = [T || {_, OtherType}=T <- [{RHS, LType}, {LHS, RType}],
+ beam_types:is_singleton_type(OtherType)],
{PosTypes, NegTypes};
infer_type({bif,'=:='}, [#b_var{}=Src,#b_literal{}=Lit], Ts, Ds) ->
diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl
index d4bd97bae0..b5295147e5 100644
--- a/lib/compiler/src/beam_validator.erl
+++ b/lib/compiler/src/beam_validator.erl
@@ -1502,9 +1502,13 @@ validate_bs_skip_1(Fail, Ctx, Stride, Live, Vst) ->
end).
advance_bs_context(Ctx, Stride, Vst0) ->
- %% slots/valid must remain untouched to support +r21.
+ %% slots/valid must remain untouched to support +r21, and the prior unit
+ %% must be retained if we _KNOW_ we won't advance.
CtxType0 = get_raw_type(Ctx, Vst0),
- CtxType = CtxType0#t_bs_context{ tail_unit=max(1, Stride) },
+ CtxType = case Stride of
+ 0 -> CtxType0;
+ N -> CtxType0#t_bs_context{ tail_unit=N }
+ end,
Vst = update_type(fun join/2, CtxType, Ctx, Vst0),
@@ -1990,24 +1994,6 @@ infer_types_1(#value{op={bif,'=/='},args=[LHS,RHS]}, Val, Op, Vst) ->
_ ->
Vst
end;
-infer_types_1(#value{op={bif,element},args=[{integer,Index},Tuple]},
- Val, Op, Vst) when Index >= 1 ->
- ElementType = get_term_type(Val, Vst),
- Es = beam_types:set_tuple_element(Index, ElementType, #{}),
- TupleType = #t_tuple{size=Index,elements=Es},
- case Op of
- eq_exact ->
- update_type(fun meet/2, TupleType, Tuple, Vst);
- ne_exact ->
- %% Subtraction is only safe when ElementType is single-valued and
- %% the index is below the tuple element limit.
- case beam_types:is_singleton_type(ElementType) of
- true when Es =/= #{} ->
- update_type(fun subtract/2, TupleType, Tuple, Vst);
- _ ->
- Vst
- end
- end;
infer_types_1(#value{op={bif,is_atom},args=[Src]}, Val, Op, Vst) ->
infer_type_test_bif(#t_atom{}, Src, Val, Op, Vst);
infer_types_1(#value{op={bif,is_boolean},args=[Src]}, Val, Op, Vst) ->
@@ -2129,7 +2115,7 @@ override_type(Type, Reg, Vst) ->
%% This is used when linear code finds out more and more information about a
%% type, so that the type gets more specialized.
-update_type(Merge, With, #value_ref{}=Ref, Vst) ->
+update_type(Merge, With, #value_ref{}=Ref, Vst0) ->
%% If the old type can't be merged with the new one, the type information
%% is inconsistent and we know that some instructions will never be
%% executed at run-time. For example:
@@ -2148,10 +2134,13 @@ update_type(Merge, With, #value_ref{}=Ref, Vst) ->
%% We therefore throw a 'type_conflict' error instead, which causes
%% validation to fail unless we're in a context where such errors can be
%% handled, such as in a branch handler.
- Current = get_raw_type(Ref, Vst),
+ Current = get_raw_type(Ref, Vst0),
case Merge(Current, With) of
- none -> throw({type_conflict, Current, With});
- Type -> set_type(Type, Ref, Vst)
+ none ->
+ throw({type_conflict, Current, With});
+ Type ->
+ Vst = update_container_type(Type, Ref, Vst0),
+ set_type(Type, Ref, Vst)
end;
update_type(Merge, With, {Kind,_}=Reg, Vst) when Kind =:= x; Kind =:= y ->
update_type(Merge, With, get_reg_vref(Reg, Vst), Vst);
@@ -2164,6 +2153,18 @@ update_type(Merge, With, Literal, Vst) ->
_Type -> Vst
end.
+%% Updates the container the given value was extracted from, if any.
+update_container_type(Type, Ref, #vst{current=#st{vs=Vs}}=Vst) ->
+ case Vs of
+ #{ Ref := #value{op={bif,element},
+ args=[{integer,Index},Tuple]} } when Index >= 1 ->
+ Es = beam_types:set_tuple_element(Index, Type, #{}),
+ TupleType = #t_tuple{size=Index,elements=Es},
+ update_type(fun meet/2, TupleType, Tuple, Vst);
+ #{} ->
+ Vst
+ end.
+
update_eq_types(LHS, RHS, Vst0) ->
LType = get_term_type(LHS, Vst0),
RType = get_term_type(RHS, Vst0),
diff --git a/lib/compiler/src/erl_bifs.erl b/lib/compiler/src/erl_bifs.erl
index e0fa8502a1..567e7e8f42 100644
--- a/lib/compiler/src/erl_bifs.erl
+++ b/lib/compiler/src/erl_bifs.erl
@@ -188,8 +188,13 @@ is_pure(_, _, _) -> false.
%% and does not affect the state (although the value it returns
%% might depend on the state).
%%
-%% Note: is_function/2 and is_record/3 are NOT safe: is_function(X, foo)
-%% and is_record(X, foo, bar) will fail.
+%% NOTES
+%%
+%% is_function/2 is not safe: is_function(X, foo) will fail.
+%%
+%% is_record/3 is not safe: is_record(X, foo, bar) will fail.
+%%
+%% erlang:make_fun/3 is safe: erlang:make_fun3(foo, bar, baz) will fail.
-spec is_safe(atom(), atom(), arity()) -> boolean().
@@ -223,7 +228,6 @@ is_safe(erlang, is_port, 1) -> true;
is_safe(erlang, is_reference, 1) -> true;
is_safe(erlang, is_tuple, 1) -> true;
is_safe(erlang, make_ref, 0) -> true;
-is_safe(erlang, make_fun, 3) -> true;
is_safe(erlang, max, 2) -> true;
is_safe(erlang, min, 2) -> true;
is_safe(erlang, node, 0) -> true;
diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl
index d318fab15c..2be6568977 100644
--- a/lib/compiler/src/v3_core.erl
+++ b/lib/compiler/src/v3_core.erl
@@ -1717,8 +1717,17 @@ bc_add_list_1([H|T], Pre, E, St0) ->
bc_add_list_1([], Pre, E, St) ->
{E,reverse(Pre),St}.
-bc_gen_size(Q, EVs, St) ->
- bc_gen_size_1(Q, EVs, #c_literal{val=1}, [], St).
+bc_gen_size([_]=Q, EVs, St) ->
+ %% Single generator.
+ bc_gen_size_1(Q, EVs, #c_literal{val=1}, [], St);
+bc_gen_size(_, _, _) ->
+ %% There are multiple generators (or there are filters).
+ %% To avoid introducing unbound variables in the size
+ %% calculation when one generator references a
+ %% variable bound by a previous generator, we will
+ %% not do any size calculation. This issue will be
+ %% handled in a cleaner way in OTP 24.
+ throw(impossible).
bc_gen_size_1([{generate,L,El,Gen}|Qs], EVs, E0, Pre0, St0) ->
bc_verify_non_filtering(El, EVs),
diff --git a/lib/compiler/test/beam_type_SUITE.erl b/lib/compiler/test/beam_type_SUITE.erl
index 5189afde58..65e4e59be9 100644
--- a/lib/compiler/test/beam_type_SUITE.erl
+++ b/lib/compiler/test/beam_type_SUITE.erl
@@ -25,7 +25,7 @@
cons/1,tuple/1,record_float/1,binary_float/1,float_compare/1,
arity_checks/1,elixir_binaries/1,find_best/1,
test_size/1,cover_lists_functions/1,list_append/1,bad_binary_unit/1,
- none_argument/1,success_type_oscillation/1]).
+ none_argument/1,success_type_oscillation/1,type_subtraction/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
@@ -52,7 +52,8 @@ groups() ->
list_append,
bad_binary_unit,
none_argument,
- success_type_oscillation
+ success_type_oscillation,
+ type_subtraction
]}].
init_per_suite(Config) ->
@@ -596,5 +597,41 @@ sto_1(case_4_1) -> {b, [sto_1(case_3_1)]};
sto_1(case_4_2) -> {b, [sto_1(case_3_2)]};
sto_1(step_4_3) -> {b, [sto_1(case_3_3)]}.
+%% ERL-1440: On inequality, we subtracted the type *common to* both variables
+%% rather than the left-hand type from the right-hand variable and vice versa,
+%% giving an erroneously narrow type.
+%%
+%% In the test below, we have functions returning integers ranged 1..2 and
+%% 2..3 and test for their equality. We know that it can only succeed when both
+%% return 2, but they can fail when the former returns 1 or the latter returns
+%% 3, so we must not subtract 2 on the failure path.
+type_subtraction(Config) when is_list(Config) ->
+ true = type_subtraction_1(id(<<"A">>)),
+ ok.
+
+type_subtraction_1(_x@1) ->
+ _a@1 = ts_12(_x@1),
+ _b@1 = ts_23(_x@1),
+ case _a@1 /= _b@1 of
+ false -> error;
+ true -> _a@1 =:= 3 andalso _b@1 =:= 2
+ end.
+
+ts_12(_x@1) ->
+ case _x@1 == <<"A">> of
+ false ->
+ 2;
+ true ->
+ 3
+ end.
+
+ts_23(_x@1) ->
+ case _x@1 == <<"A">> of
+ false ->
+ 1;
+ true ->
+ 2
+ end.
+
id(I) ->
I.
diff --git a/lib/compiler/test/beam_validator_SUITE.erl b/lib/compiler/test/beam_validator_SUITE.erl
index 1ee3ea79ca..6f6391d3ba 100644
--- a/lib/compiler/test/beam_validator_SUITE.erl
+++ b/lib/compiler/test/beam_validator_SUITE.erl
@@ -39,7 +39,7 @@
branch_to_try_handler/1,call_without_stack/1,
receive_marker/1,safe_instructions/1,
missing_return_type/1,will_bif_succeed/1,
- bs_saved_position_units/1]).
+ bs_saved_position_units/1,parent_container/1]).
-include_lib("common_test/include/ct.hrl").
@@ -73,7 +73,7 @@ groups() ->
branch_to_try_handler,call_without_stack,
receive_marker,safe_instructions,
missing_return_type,will_bif_succeed,
- bs_saved_position_units]}].
+ bs_saved_position_units,parent_container]}].
init_per_suite(Config) ->
test_lib:recompile(?MODULE),
@@ -964,5 +964,23 @@ f1(body) when map_get(girl, #{friend => node()}); [], community ->
f1(body) ->
ok.
+%% ERL-1426: When a value was extracted from a tuple, subsequent type tests did
+%% not update the type of said tuple.
+
+-record(pc, {a}).
+
+parent_container(_Config) ->
+ ok = pc_1(id(#pc{a=true})).
+
+pc_1(#pc{a=A}=R) ->
+ case A of
+ true -> ok;
+ false -> ok
+ end,
+ ok = pc_2(R).
+
+pc_2(_R) ->
+ ok.
+
id(I) ->
I.
diff --git a/lib/compiler/test/bs_bincomp_SUITE.erl b/lib/compiler/test/bs_bincomp_SUITE.erl
index 9ac261c805..0d129a4a48 100644
--- a/lib/compiler/test/bs_bincomp_SUITE.erl
+++ b/lib/compiler/test/bs_bincomp_SUITE.erl
@@ -101,26 +101,26 @@ extended_bit_aligned(Config) when is_list(Config) ->
mixed(Config) when is_list(Config) ->
cs_init(),
<<2,3,3,4,4,5,5,6>> =
- cs(<< <<(X+Y)>> || <<X>> <= <<1,2,3,4>>, <<Y>> <= <<1,2>> >>),
+ cs_default(<< <<(X+Y)>> || <<X>> <= <<1,2,3,4>>, <<Y>> <= <<1,2>> >>),
<<2,3,3,4,4,5,5,6>> =
- << <<(X+Y)>> || <<X>> <= <<1,2,3,4>>, Y <- [1,2] >>,
+ cs_default(<< <<(X+Y)>> || <<X>> <= <<1,2,3,4>>, Y <- [1,2] >>),
<<2,3,3,4,4,5,5,6>> =
- cs(<< <<(X+Y)>> || X <- [1,2,3,4], Y <- [1,2] >>),
+ cs_default(<< <<(X+Y)>> || X <- [1,2,3,4], Y <- [1,2] >>),
One = id([1,2,3,4]),
Two = id([1,2]),
<<2,3,3,4,4,5,5,6>> =
- cs(<< <<(X+Y)>> || X <- One, Y <- Two >>),
+ cs_default(<< <<(X+Y)>> || X <- One, Y <- Two >>),
[2,3,3,4,4,5,5,6] =
[(X+Y) || <<X>> <= <<1,2,3,4>>, <<Y>> <= <<1,2>>],
[2,3,3,4,4,5,5,6] =
[(X+Y) || <<X>> <= <<1,2,3,4>>, Y <- [1,2]],
<<2:3,3:3,3:3,4:3,4:3,5:3,5:3,6:3>> =
- cs(<< <<(X+Y):3>> || <<X:3>> <= <<1:3,2:3,3:3,4:3>>,
- <<Y:3>> <= <<1:3,2:3>> >>),
+ cs_default(<< <<(X+Y):3>> || <<X:3>> <= <<1:3,2:3,3:3,4:3>>,
+ <<Y:3>> <= <<1:3,2:3>> >>),
<<2:3,3:3,3:3,4:3,4:3,5:3,5:3,6:3>> =
- cs(<< <<(X+Y):3>> || <<X:3>> <= <<1:3,2:3,3:3,4:3>>, Y <- [1,2] >>),
+ cs_default(<< <<(X+Y):3>> || <<X:3>> <= <<1:3,2:3,3:3,4:3>>, Y <- [1,2] >>),
<<2:3,3:3,3:3,4:3,4:3,5:3,5:3,6:3>> =
- cs(<< <<(X+Y):3>> || X <- [1,2,3,4], Y <- [1,2] >>),
+ cs_default(<< <<(X+Y):3>> || X <- [1,2,3,4], Y <- [1,2] >>),
<<2:3,3:3,3:3,4:3,4:3,5:3,5:3,6:3>> =
cs_default(<< <<(X+Y):3>> || {X,Y} <- [{1,1},{1,2},{2,1},{2,2},
{3,1},{3,2},{4,1},{4,2}] >>),
@@ -128,8 +128,17 @@ mixed(Config) when is_list(Config) ->
[(X+Y) || <<X:3>> <= <<1:3,2:3,3:3,4:3>>, <<Y:3>> <= <<1:3,2:3>>],
[2,3,3,4,4,5,5,6] =
[(X+Y) || <<X:3>> <= <<1:3,2:3,3:3,4:3>>, {_,Y} <- [{a,1},{b,2}]],
+
+ %% OTP-16899: Nested binary comprehensions would fail to load.
+ <<0,1,0,2,0,3,99>> = mixed_nested([1,2,3]),
+
+ <<1>> = cs_default(<< <<X>> || L <- [[1]], X <- L >>),
+
cs_end().
+mixed_nested(L) ->
+ << << << << E:16 >> || E <- L >> || true >>/binary, 99:(id(8))>>.
+
filters(Config) when is_list(Config) ->
cs_init(),
<<"BDF">> =
@@ -200,6 +209,11 @@ nomatch(Config) when is_list(Config) ->
<<>> = << <<X:32>> || <<X:all/binary>> <= Bin >>,
<<>> = << <<X:32>> || <<X:bad/binary>> <= Bin >>,
+ <<>> = << <<"a">> || <<_:1/float>> <= Bin>>,
+
+ NaN = <<(-1):32>>,
+ <<>> = << <<"a">> || <<_:32/float>> <= NaN >>,
+
ok.
sizes(Config) when is_list(Config) ->
diff --git a/lib/compiler/test/bs_construct_SUITE.erl b/lib/compiler/test/bs_construct_SUITE.erl
index b247506212..d0a1d07861 100644
--- a/lib/compiler/test/bs_construct_SUITE.erl
+++ b/lib/compiler/test/bs_construct_SUITE.erl
@@ -663,8 +663,10 @@ bad_size(_Config) ->
{'EXIT',{badarg,_}} = (catch bad_float_size(<<"abc">>)),
{'EXIT',{badarg,_}} = (catch bad_integer_size()),
{'EXIT',{badarg,_}} = (catch bad_integer_size(<<"xyz">>)),
+ {'EXIT',{badarg,_}} = (catch bad_integer_size2()),
{'EXIT',{badarg,_}} = (catch bad_binary_size()),
{'EXIT',{badarg,_}} = (catch bad_binary_size(<<"xyz">>)),
+ {'EXIT',{badarg,_}} = (catch bad_binary_size2()),
ok.
bad_float_size() ->
@@ -679,8 +681,18 @@ bad_integer_size() ->
bad_integer_size(Bin) ->
<<Bin/binary,0:case 0 of 0 -> art end/integer>>.
+bad_integer_size2() ->
+ <<
+ <<(id(42))>>:[ <<>> || <<123:true>> <= <<>> ],
+ <<(id(100))>>:7>>.
+
bad_binary_size() ->
<<<<"abc">>:case 0 of 0 -> art end/binary>>.
bad_binary_size(Bin) ->
<<Bin/binary,<<"abc">>:case 0 of 0 -> art end/binary>>.
+
+bad_binary_size2() ->
+ <<
+ <<(id(42))>>:[ <<>> || <<123:true>> <= <<>> ]/binary,
+ <<(id(100))>>:7>>.
diff --git a/lib/compiler/test/bs_match_SUITE.erl b/lib/compiler/test/bs_match_SUITE.erl
index 372fa388b9..8748cc8f46 100644
--- a/lib/compiler/test/bs_match_SUITE.erl
+++ b/lib/compiler/test/bs_match_SUITE.erl
@@ -1336,6 +1336,7 @@ bad_size(Config) when is_list(Config) ->
Tuple = {a,b,c},
Binary = <<1,2,3>>,
Atom = an_atom,
+ NaN = <<(-1):32>>,
{'EXIT',{{badmatch,<<>>},_}} = (catch <<32:Tuple>> = id(<<>>)),
{'EXIT',{{badmatch,<<>>},_}} = (catch <<32:Binary>> = id(<<>>)),
@@ -1348,12 +1349,16 @@ bad_size(Config) when is_list(Config) ->
{'EXIT',{{badmatch,<<>>},_}} = (catch <<42.0:Binary/float>> = id(<<>>)),
{'EXIT',{{badmatch,<<>>},_}} = (catch <<42.0:Atom/float>> = id(<<>>)),
{'EXIT',{{badmatch,<<>>},_}} = (catch <<42.0:2.5/float>> = id(<<>>)),
+ {'EXIT',{{badmatch,<<>>},_}} = (catch <<42.0:1/float>> = id(<<>>)),
+ {'EXIT',{{badmatch,NaN},_}} = (catch <<42.0:32/float>> = id(NaN)),
%% Matched out value is ignored.
{'EXIT',{{badmatch,<<>>},_}} = (catch <<_:Binary>> = id(<<>>)),
{'EXIT',{{badmatch,<<>>},_}} = (catch <<_:Tuple>> = id(<<>>)),
{'EXIT',{{badmatch,<<>>},_}} = (catch <<_:Atom>> = id(<<>>)),
{'EXIT',{{badmatch,<<>>},_}} = (catch <<_:2.5>> = id(<<>>)),
+ {'EXIT',{{badmatch,<<1:1>>},_}} = (catch <<_:1/float>> = id(<<1:1>>)),
+ {'EXIT',{{badmatch,NaN},_}} = (catch <<_:32/float>> = id(NaN)),
no_match = bad_all_size(<<>>),
no_match = bad_all_size(<<1,2,3>>),
@@ -2396,6 +2401,9 @@ empty_get_binary(Config) when is_list(Config) ->
{<<>>, <<1,2,3>>} = egb_1(<<1,2,3>>),
{<<>>, <<>>} = egb_1(<<>>),
+ <<0,1,0,2,0,3>> = egb_2(id(<<1,2,3>>)),
+ <<>> = egb_2(id(<<>>)),
+
ok.
egb_1(Bytes) ->
@@ -2406,6 +2414,11 @@ egb_1(Bytes) ->
end,
{Term, Bytes}.
+egb_2(Bin) ->
+ <<
+ <<K,N>> || <<K:0,N>> <= Bin
+ >>.
+
id(I) -> I.
expand_and_squeeze(Config) when is_list(Config) ->
diff --git a/lib/compiler/test/fun_SUITE.erl b/lib/compiler/test/fun_SUITE.erl
index 3b8e8698de..bd8603ae81 100644
--- a/lib/compiler/test/fun_SUITE.erl
+++ b/lib/compiler/test/fun_SUITE.erl
@@ -206,11 +206,18 @@ external(Config) when is_list(Config) ->
{'EXIT',{{badarity,_},_}} = (catch (id(fun lists:sum/1))(1, 2, 3)),
{'EXIT',{{badarity,_},_}} = (catch apply(fun lists:sum/1, [1,2,3])),
+ {'EXIT',{badarg,_}} = (catch bad_external_fun()),
+
ok.
call_me(I) ->
{ok,I}.
+bad_external_fun() ->
+ V0 = idea,
+ fun V0:V0/V0, %Should fail.
+ never_reached.
+
eep37(Config) when is_list(Config) ->
F = fun Fact(N) when N > 0 -> N * Fact(N - 1); Fact(0) -> 1 end,
Add = fun _(N) -> N + 1 end,
diff --git a/lib/compiler/vsn.mk b/lib/compiler/vsn.mk
index e766a42b0b..421b5ac2a1 100644
--- a/lib/compiler/vsn.mk
+++ b/lib/compiler/vsn.mk
@@ -1 +1 @@
-COMPILER_VSN = 7.6.5
+COMPILER_VSN = 7.6.6
diff --git a/lib/crypto/c_src/Makefile.in b/lib/crypto/c_src/Makefile.in
index 786fbc1031..ecccc33d8d 100644
--- a/lib/crypto/c_src/Makefile.in
+++ b/lib/crypto/c_src/Makefile.in
@@ -54,10 +54,16 @@ ifeq ($(TYPE),valgrind)
TYPEMARKER = .valgrind
TYPE_FLAGS = $(subst -O3,,$(subst -O2,,$(CFLAGS))) -DVALGRIND
else
+ifeq ($(TYPE),gprof)
+TYPEMARKER = .gprof
+TYPE_EXTRA_CFLAGS = -DGPROF -pg
+TYPE_FLAGS = $(CFLAGS) $(TYPE_EXTRA_CFLAGS)
+else
TYPEMARKER =
TYPE_FLAGS = $(CFLAGS)
endif
endif
+endif
# ----------------------------------------------------
# Release directory specification
@@ -142,10 +148,10 @@ ifndef RANLIB
RANLIB=true
endif
-CONFIGURE_ARGS = -DDISABLE_EVP_DH=@DISABLE_EVP_DH@
+CONFIGURE_ARGS = -DDISABLE_EVP_DH=@DISABLE_EVP_DH@ -DDISABLE_EVP_HMAC=@DISABLE_EVP_HMAC@
ALL_CFLAGS = $(TYPE_FLAGS) $(EXTRA_FLAGS) $(CONFIGURE_ARGS) $(INCLUDES)
-ALL_STATIC_CFLAGS = @DED_STATIC_CFLAGS@ $(CONFIGURE_ARGS) $(INCLUDES)
+ALL_STATIC_CFLAGS = @DED_STATIC_CFLAGS@ $(TYPE_EXTRA_CFLAGS) $(CONFIGURE_ARGS) $(INCLUDES)
# ----------------------------------------------------
# Targets
diff --git a/lib/crypto/c_src/algorithms.c b/lib/crypto/c_src/algorithms.c
index 752df2c6ae..f78fcd778b 100644
--- a/lib/crypto/c_src/algorithms.c
+++ b/lib/crypto/c_src/algorithms.c
@@ -18,21 +18,57 @@
* %CopyrightEnd%
*/
+#include "common.h"
#include "algorithms.h"
#include "cipher.h"
#include "mac.h"
static unsigned int algo_hash_cnt, algo_hash_fips_cnt;
static ERL_NIF_TERM algo_hash[14]; /* increase when extending the list */
+void init_hash_types(ErlNifEnv* env);
+
static unsigned int algo_pubkey_cnt, algo_pubkey_fips_cnt;
static ERL_NIF_TERM algo_pubkey[12]; /* increase when extending the list */
+void init_pubkey_types(ErlNifEnv* env);
+
static unsigned int algo_curve_cnt, algo_curve_fips_cnt;
-static ERL_NIF_TERM algo_curve[89]; /* increase when extending the list */
+static ERL_NIF_TERM algo_curve[2][89]; /* increase when extending the list */
+void init_curve_types(ErlNifEnv* env);
+
static unsigned int algo_rsa_opts_cnt, algo_rsa_opts_fips_cnt;
static ERL_NIF_TERM algo_rsa_opts[11]; /* increase when extending the list */
+void init_rsa_opts_types(ErlNifEnv* env);
+
+
+
void init_algorithms_types(ErlNifEnv* env)
{
+ init_hash_types(env);
+ init_pubkey_types(env);
+ init_curve_types(env);
+ init_rsa_opts_types(env);
+ /* ciphers and macs are initiated statically */
+}
+
+
+
+/*================================================================
+ Hash algorithms
+*/
+
+ERL_NIF_TERM hash_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ unsigned int cnt =
+#ifdef FIPS_SUPPORT
+ FIPS_mode() ? algo_hash_fips_cnt :
+#endif
+ algo_hash_cnt;
+
+ return enif_make_list_from_array(env, algo_hash, cnt);
+}
+
+void init_hash_types(ErlNifEnv* env) {
// Validated algorithms first
algo_hash_cnt = 0;
algo_hash[algo_hash_cnt++] = atom_sha;
@@ -77,6 +113,27 @@ void init_algorithms_types(ErlNifEnv* env)
algo_hash[algo_hash_cnt++] = enif_make_atom(env, "ripemd160");
#endif
+ ASSERT(algo_hash_cnt <= sizeof(algo_hash)/sizeof(ERL_NIF_TERM));
+}
+
+
+/*================================================================
+ Public key algorithms
+*/
+
+ERL_NIF_TERM pubkey_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ unsigned int cnt =
+#ifdef FIPS_SUPPORT
+ FIPS_mode() ? algo_pubkey_fips_cnt :
+#endif
+ algo_pubkey_cnt;
+
+ return enif_make_list_from_array(env, algo_pubkey, cnt);
+}
+
+void init_pubkey_types(ErlNifEnv* env) {
+ // Validated algorithms first
algo_pubkey_cnt = 0;
algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "rsa");
#ifdef HAVE_DSA
@@ -103,111 +160,501 @@ void init_algorithms_types(ErlNifEnv* env)
#endif
algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "srp");
- // Validated algorithms first
- algo_curve_cnt = 0;
+ ASSERT(algo_pubkey_cnt <= sizeof(algo_pubkey)/sizeof(ERL_NIF_TERM));
+}
+
+
+/*================================================================
+ Cipher key algorithms
+*/
+
+ERL_NIF_TERM cipher_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ return cipher_types_as_list(env); /* Exclude old api ciphers */
+}
+
+
+/*================================================================
+ MAC key algorithms
+*/
+
+ERL_NIF_TERM mac_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ return mac_types_as_list(env);
+}
+
+
+/*================================================================
+ Curves
+*/
+
+ERL_NIF_TERM curve_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+#ifdef FIPS_SUPPORT
+ if (FIPS_mode())
+ return enif_make_list_from_array(env, algo_curve[1], algo_curve_fips_cnt);
+ else
+#endif
+ return enif_make_list_from_array(env, algo_curve[0], algo_curve_cnt);
+}
+
#if defined(HAVE_EC)
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp160k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp160r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp160r2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp192r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp192k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp224k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp224r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp256k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp256r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp384r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp521r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime192v1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime192v2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime192v3");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime239v1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime239v2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime239v3");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime256v1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls7");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls9");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls12");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP160r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP160t1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP192r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP192t1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP224r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP224t1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP256r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP256t1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP320r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP320t1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP384r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP384t1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP512r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP512t1");
-#if !defined(OPENSSL_NO_EC2M)
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect163k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect163r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect163r2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect193r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect193r2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect233k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect233r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect239k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect283k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect283r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect409k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect409r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect571k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect571r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb163v1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb163v2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb163v3");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb176v1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb191v1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb191v2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb191v3");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb208w1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb239v1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb239v2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb239v3");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb272w1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb304w1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb359v1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb368w1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb431r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls3");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls5");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls10");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls11");
+int init_curves(ErlNifEnv* env, int fips);
+int valid_curve(int nid);
+#endif
+
+void init_curve_types(ErlNifEnv* env) {
+ algo_curve_cnt = algo_curve_fips_cnt = 0;
+#if defined(HAVE_EC)
+#ifdef FIPS_SUPPORT
+ if (FIPS_mode()) {
+ // enabled
+ algo_curve_fips_cnt = init_curves(env, 1);
+ FIPS_mode_set(0); // disable
+ algo_curve_cnt = init_curves(env, 0);
+ FIPS_mode_set(1); // re-enable
+ } else {
+ // disabled
+ algo_curve_cnt = init_curves(env, 0);
+ FIPS_mode_set(1); // enable
+ algo_curve_fips_cnt = init_curves(env, 1);
+ FIPS_mode_set(0); // re-disable
+ }
+#else
+ // No fips support
+ algo_curve_cnt = algo_curve_fips_cnt = init_curves(env, 0);
+#endif
+#endif /* defined(HAVE_EC) */
+
+ ASSERT(algo_curve_cnt+algo_curve_fips_cnt <= sizeof(algo_curve)/sizeof(ERL_NIF_TERM));
+}
+
+
+#if defined(HAVE_EC)
+int init_curves(ErlNifEnv* env, int fips) {
+ int cnt = 0;
+
+#ifdef NID_secp160k1
+ if (valid_curve(NID_secp160k1)) algo_curve[fips][cnt++] = enif_make_atom(env,"secp160k1");
+#else
+#endif
+#ifdef NID_secp160r1
+ if (valid_curve(NID_secp160r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"secp160r1");
+#else
+#endif
+#ifdef NID_secp160r2
+ if (valid_curve(NID_secp160r2)) algo_curve[fips][cnt++] = enif_make_atom(env,"secp160r2");
+#else
+#endif
+#ifdef NID_secp192k1
+ if (valid_curve(NID_secp192k1)) algo_curve[fips][cnt++] = enif_make_atom(env,"secp192k1");
+#else
+#endif
+#ifdef NID_secp224k1
+ if (valid_curve(NID_secp224k1)) algo_curve[fips][cnt++] = enif_make_atom(env,"secp224k1");
+#else
+#endif
+#ifdef NID_secp224r1
+ if (valid_curve(NID_secp224r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"secp224r1");
+#else
+#endif
+#ifdef NID_secp256k1
+ if (valid_curve(NID_secp256k1)) algo_curve[fips][cnt++] = enif_make_atom(env,"secp256k1");
+#else
+#endif
+#ifdef NID_secp384r1
+ if (valid_curve(NID_secp384r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"secp384r1");
+#else
+#endif
+#ifdef NID_secp521r1
+ if (valid_curve(NID_secp521r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"secp521r1");
+#else
+#endif
+#ifdef NID_X9_62_prime192v1
+ if (valid_curve(NID_X9_62_prime192v1)) {
+ algo_curve[fips][cnt++] = enif_make_atom(env,"secp192r1");
+ algo_curve[fips][cnt++] = enif_make_atom(env,"prime192v1");
+ }
+#else
+#endif
+#ifdef NID_X9_62_prime192v2
+ if (valid_curve(NID_X9_62_prime192v2)) algo_curve[fips][cnt++] = enif_make_atom(env,"prime192v2");
+#else
+#endif
+#ifdef NID_X9_62_prime192v3
+ if (valid_curve(NID_X9_62_prime192v3)) algo_curve[fips][cnt++] = enif_make_atom(env,"prime192v3");
+#else
+#endif
+#ifdef NID_X9_62_prime239v1
+ if (valid_curve(NID_X9_62_prime239v1)) algo_curve[fips][cnt++] = enif_make_atom(env,"prime239v1");
+#else
+#endif
+#ifdef NID_X9_62_prime239v2
+ if (valid_curve(NID_X9_62_prime239v2)) algo_curve[fips][cnt++] = enif_make_atom(env,"prime239v2");
+#else
+#endif
+#ifdef NID_X9_62_prime239v3
+ if (valid_curve(NID_X9_62_prime239v3)) algo_curve[fips][cnt++] = enif_make_atom(env,"prime239v3");
+#else
+#endif
+#ifdef NID_X9_62_prime256v1
+ if (valid_curve(NID_X9_62_prime256v1)) {
+ algo_curve[fips][cnt++] = enif_make_atom(env,"secp256r1");
+ algo_curve[fips][cnt++] = enif_make_atom(env,"prime256v1");
+ }
+#else
+#endif
+#ifdef NID_wap_wsg_idm_ecid_wtls7
+ if (valid_curve(NID_wap_wsg_idm_ecid_wtls7)) algo_curve[fips][cnt++] = enif_make_atom(env,"wtls7");
+#else
+#endif
+#ifdef NID_wap_wsg_idm_ecid_wtls9
+ if (valid_curve(NID_wap_wsg_idm_ecid_wtls9)) algo_curve[fips][cnt++] = enif_make_atom(env,"wtls9");
+#else
+#endif
+#ifdef NID_wap_wsg_idm_ecid_wtls12
+ if (valid_curve(NID_wap_wsg_idm_ecid_wtls12)) algo_curve[fips][cnt++] = enif_make_atom(env,"wtls12");
+#else
+#endif
+#ifdef NID_brainpoolP160r1
+ if (valid_curve(NID_brainpoolP160r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"brainpoolP160r1");
+#else
+#endif
+#ifdef NID_brainpoolP160t1
+ if (valid_curve(NID_brainpoolP160t1)) algo_curve[fips][cnt++] = enif_make_atom(env,"brainpoolP160t1");
+#else
+#endif
+#ifdef NID_brainpoolP192r1
+ if (valid_curve(NID_brainpoolP192r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"brainpoolP192r1");
+#else
+#endif
+#ifdef NID_brainpoolP192t1
+ if (valid_curve(NID_brainpoolP192t1)) algo_curve[fips][cnt++] = enif_make_atom(env,"brainpoolP192t1");
+#else
+#endif
+#ifdef NID_brainpoolP224r1
+ if (valid_curve(NID_brainpoolP224r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"brainpoolP224r1");
+#else
+#endif
+#ifdef NID_brainpoolP224t1
+ if (valid_curve(NID_brainpoolP224t1)) algo_curve[fips][cnt++] = enif_make_atom(env,"brainpoolP224t1");
+#else
+#endif
+#ifdef NID_brainpoolP256r1
+ if (valid_curve(NID_brainpoolP256r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"brainpoolP256r1");
+#else
+#endif
+#ifdef NID_brainpoolP256t1
+ if (valid_curve(NID_brainpoolP256t1)) algo_curve[fips][cnt++] = enif_make_atom(env,"brainpoolP256t1");
+#else
+#endif
+#ifdef NID_brainpoolP320r1
+ if (valid_curve(NID_brainpoolP320r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"brainpoolP320r1");
+#else
+#endif
+#ifdef NID_brainpoolP320t1
+ if (valid_curve(NID_brainpoolP320t1)) algo_curve[fips][cnt++] = enif_make_atom(env,"brainpoolP320t1");
+#else
+#endif
+#ifdef NID_brainpoolP384r1
+ if (valid_curve(NID_brainpoolP384r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"brainpoolP384r1");
+#else
+#endif
+#ifdef NID_brainpoolP384t1
+ if (valid_curve(NID_brainpoolP384t1)) algo_curve[fips][cnt++] = enif_make_atom(env,"brainpoolP384t1");
+#else
+#endif
+#ifdef NID_brainpoolP512r1
+ if (valid_curve(NID_brainpoolP512r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"brainpoolP512r1");
+#else
+#endif
+#ifdef NID_brainpoolP512t1
+ if (valid_curve(NID_brainpoolP512t1)) algo_curve[fips][cnt++] = enif_make_atom(env,"brainpoolP512t1");
+#else
+#endif
+ //#if !defined(OPENSSL_NO_EC2M)
+#ifdef NID_sect163k1
+ if (valid_curve(NID_sect163k1)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect163k1");
+#else
+#endif
+#ifdef NID_sect163r1
+ if (valid_curve(NID_sect163r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect163r1");
+#else
+#endif
+#ifdef NID_sect163r2
+ if (valid_curve(NID_sect163r2)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect163r2");
+#else
+#endif
+#ifdef NID_sect193r1
+ if (valid_curve(NID_sect193r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect193r1");
+#else
+#endif
+#ifdef NID_sect193r2
+ if (valid_curve(NID_sect193r2)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect193r2");
+#else
+#endif
+#ifdef NID_sect233k1
+ if (valid_curve(NID_sect233k1)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect233k1");
+#else
+#endif
+#ifdef NID_sect233r1
+ if (valid_curve(NID_sect233r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect233r1");
+#else
+#endif
+#ifdef NID_sect239k1
+ if (valid_curve(NID_sect239k1)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect239k1");
+#else
+#endif
+#ifdef NID_sect283k1
+ if (valid_curve(NID_sect283k1)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect283k1");
+#else
+#endif
+#ifdef NID_sect283r1
+ if (valid_curve(NID_sect283r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect283r1");
+#else
+#endif
+#ifdef NID_sect409k1
+ if (valid_curve(NID_sect409k1)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect409k1");
+#else
+#endif
+#ifdef NID_sect409r1
+ if (valid_curve(NID_sect409r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect409r1");
+#else
+#endif
+#ifdef NID_sect571k1
+ if (valid_curve(NID_sect571k1)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect571k1");
+#else
#endif
+#ifdef NID_sect571r1
+ if (valid_curve(NID_sect571r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect571r1");
+#else
+#endif
+#ifdef NID_X9_62_c2pnb163v1
+ if (valid_curve(NID_X9_62_c2pnb163v1)) algo_curve[fips][cnt++] = enif_make_atom(env,"c2pnb163v1");
+#else
+#endif
+#ifdef NID_X9_62_c2pnb163v2
+ if (valid_curve(NID_X9_62_c2pnb163v2)) algo_curve[fips][cnt++] = enif_make_atom(env,"c2pnb163v2");
+#else
+#endif
+#ifdef NID_X9_62_c2pnb163v3
+ if (valid_curve(NID_X9_62_c2pnb163v3)) algo_curve[fips][cnt++] = enif_make_atom(env,"c2pnb163v3");
+#else
+#endif
+#ifdef NID_X9_62_c2pnb176v1
+ if (valid_curve(NID_X9_62_c2pnb176v1)) algo_curve[fips][cnt++] = enif_make_atom(env,"c2pnb176v1");
+#else
+#endif
+#ifdef NID_X9_62_c2tnb191v1
+ if (valid_curve(NID_X9_62_c2tnb191v1)) algo_curve[fips][cnt++] = enif_make_atom(env,"c2tnb191v1");
+#else
+#endif
+#ifdef NID_X9_62_c2tnb191v2
+ if (valid_curve(NID_X9_62_c2tnb191v2)) algo_curve[fips][cnt++] = enif_make_atom(env,"c2tnb191v2");
+#else
+#endif
+#ifdef NID_X9_62_c2tnb191v3
+ if (valid_curve(NID_X9_62_c2tnb191v3)) algo_curve[fips][cnt++] = enif_make_atom(env,"c2tnb191v3");
+#else
+#endif
+#ifdef NID_X9_62_c2pnb208w1
+ if (valid_curve(NID_X9_62_c2pnb208w1)) algo_curve[fips][cnt++] = enif_make_atom(env,"c2pnb208w1");
+#else
+#endif
+#ifdef NID_X9_62_c2tnb239v1
+ if (valid_curve(NID_X9_62_c2tnb239v1)) algo_curve[fips][cnt++] = enif_make_atom(env,"c2tnb239v1");
+#else
+#endif
+#ifdef NID_X9_62_c2tnb239v2
+ if (valid_curve(NID_X9_62_c2tnb239v2)) algo_curve[fips][cnt++] = enif_make_atom(env,"c2tnb239v2");
+#else
+#endif
+#ifdef NID_X9_62_c2tnb239v3
+ if (valid_curve(NID_X9_62_c2tnb239v3)) algo_curve[fips][cnt++] = enif_make_atom(env,"c2tnb239v3");
+#else
+#endif
+#ifdef NID_X9_62_c2pnb272w1
+ if (valid_curve(NID_X9_62_c2pnb272w1)) algo_curve[fips][cnt++] = enif_make_atom(env,"c2pnb272w1");
+#else
+#endif
+#ifdef NID_X9_62_c2pnb304w1
+ if (valid_curve(NID_X9_62_c2pnb304w1)) algo_curve[fips][cnt++] = enif_make_atom(env,"c2pnb304w1");
+#else
+#endif
+#ifdef NID_X9_62_c2tnb359v1
+ if (valid_curve(NID_X9_62_c2tnb359v1)) algo_curve[fips][cnt++] = enif_make_atom(env,"c2tnb359v1");
+#else
+#endif
+#ifdef NID_X9_62_c2pnb368w1
+ if (valid_curve(NID_X9_62_c2pnb368w1)) algo_curve[fips][cnt++] = enif_make_atom(env,"c2pnb368w1");
+#else
+#endif
+#ifdef NID_X9_62_c2tnb431r1
+ if (valid_curve(NID_X9_62_c2tnb431r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"c2tnb431r1");
+#else
+#endif
+#ifdef NID_wap_wsg_idm_ecid_wtls3
+ if (valid_curve(NID_wap_wsg_idm_ecid_wtls3)) algo_curve[fips][cnt++] = enif_make_atom(env,"wtls3");
+#else
+#endif
+#ifdef NID_wap_wsg_idm_ecid_wtls5
+ if (valid_curve(NID_wap_wsg_idm_ecid_wtls5)) algo_curve[fips][cnt++] = enif_make_atom(env,"wtls5");
+#else
+#endif
+#ifdef NID_wap_wsg_idm_ecid_wtls10
+ if (valid_curve(NID_wap_wsg_idm_ecid_wtls10)) algo_curve[fips][cnt++] = enif_make_atom(env,"wtls10");
+#else
+#endif
+#ifdef NID_wap_wsg_idm_ecid_wtls11
+ if (valid_curve(NID_wap_wsg_idm_ecid_wtls11)) algo_curve[fips][cnt++] = enif_make_atom(env,"wtls11");
+#else
#endif
// Non-validated algorithms follow
- algo_curve_fips_cnt = algo_curve_cnt;
-#if defined(HAVE_EC)
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp112r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp112r2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp128r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp128r2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls6");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls8");
-#if !defined(OPENSSL_NO_EC2M)
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect113r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect113r2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect131r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect131r2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls4");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"ipsec3");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"ipsec4");
+#ifdef NID_secp112r1
+ if (valid_curve(NID_secp112r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"secp112r1");
+#else
+#endif
+#ifdef NID_secp112r2
+ if (valid_curve(NID_secp112r2)) algo_curve[fips][cnt++] = enif_make_atom(env,"secp112r2");
+#else
+#endif
+#ifdef NID_secp128r1
+ if (valid_curve(NID_secp128r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"secp128r1");
+#else
+#endif
+#ifdef NID_secp128r2
+ if (valid_curve(NID_secp128r2)) algo_curve[fips][cnt++] = enif_make_atom(env,"secp128r2");
+#else
+#endif
+#ifdef NID_wap_wsg_idm_ecid_wtls6
+ if (valid_curve(NID_wap_wsg_idm_ecid_wtls6)) algo_curve[fips][cnt++] = enif_make_atom(env,"wtls6");
+#else
+#endif
+#ifdef NID_wap_wsg_idm_ecid_wtls8
+ if (valid_curve(NID_wap_wsg_idm_ecid_wtls8)) algo_curve[fips][cnt++] = enif_make_atom(env,"wtls8");
+#else
+#endif
+ //#if !defined(OPENSSL_NO_EC2M)
+#ifdef NID_sect113r1
+ if (valid_curve(NID_sect113r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect113r1");
+#else
+#endif
+#ifdef NID_sect113r2
+ if (valid_curve(NID_sect113r2)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect113r2");
+#else
+#endif
+#ifdef NID_sect131r1
+ if (valid_curve(NID_sect131r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect131r1");
+#else
#endif
+#ifdef NID_sect131r2
+ if (valid_curve(NID_sect131r2)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect131r2");
+#else
#endif
- //--
+#ifdef NID_wap_wsg_idm_ecid_wtls1
+ if (valid_curve(NID_wap_wsg_idm_ecid_wtls1)) algo_curve[fips][cnt++] = enif_make_atom(env,"wtls1");
+#else
+#endif
+#ifdef NID_wap_wsg_idm_ecid_wtls4
+ if (valid_curve(NID_wap_wsg_idm_ecid_wtls4)) algo_curve[fips][cnt++] = enif_make_atom(env,"wtls4");
+#else
+#endif
+#ifdef NID_ipsec3
+ if (valid_curve(NID_ipsec3)) algo_curve[fips][cnt++] = enif_make_atom(env,"ipsec3");
+#else
+#endif
+#ifdef NID_ipsec4
+ if (valid_curve(NID_ipsec4)) algo_curve[fips][cnt++] = enif_make_atom(env,"ipsec4");
+#else
+#endif
+
+ if (!fips) {
#ifdef HAVE_EDDSA
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"ed25519");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"ed448");
+ algo_curve[fips][cnt++] = enif_make_atom(env,"ed25519");
+ algo_curve[fips][cnt++] = enif_make_atom(env,"ed448");
#endif
#ifdef HAVE_EDDH
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"x25519");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"x448");
+ algo_curve[fips][cnt++] = enif_make_atom(env,"x25519");
+ algo_curve[fips][cnt++] = enif_make_atom(env,"x448");
+#endif
+ }
+
+ return cnt;
+}
+
+/* Check if the curve in nid is supported by the
+ current cryptolib and current FIPS state.
+*/
+
+int valid_curve(int nid) {
+ int ret = 0;
+
+#if defined(HAVE_DH)
+# if defined(HAS_EVP_PKEY_CTX) && (! DISABLE_EVP_DH)
+ EVP_PKEY_CTX *pctx = NULL, *kctx = NULL;
+ EVP_PKEY *pkey = NULL, *params = NULL;
+
+ if (NULL == (pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL)))
+ goto out;
+
+ if (1 != EVP_PKEY_paramgen_init(pctx))
+ goto out;
+
+ if (1 != EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, nid))
+ goto out;
+
+ if (!EVP_PKEY_paramgen(pctx, &params))
+ goto out;
+
+ if (NULL == (kctx = EVP_PKEY_CTX_new(params, NULL)))
+ goto out;
+
+ if(1 != EVP_PKEY_keygen_init(kctx))
+ goto out;
+ if (1 != EVP_PKEY_keygen(kctx, &pkey))
+ goto out;
+ ret = 1;
+ out:
+ if (pkey) EVP_PKEY_free(pkey);
+ if (kctx) EVP_PKEY_CTX_free(kctx);
+ if (params) EVP_PKEY_free(params);
+ if (pctx) EVP_PKEY_CTX_free(pctx);
+
+# else
+ EC_KEY *key;
+
+ if (NULL == (key = EC_KEY_new_by_curve_name(nid)))
+ goto out;
+
+ if(1 != EC_KEY_generate_key(key))
+ goto out;
+
+ ret = 1;
+ out:
+ if (key) EC_KEY_free(key);
+# endif
+#endif /* HAVE_DH etc */
+
+ return ret;
+}
+#endif /* HAVE_EC */
+
+/*================================================================
+ RSA Options
+*/
+
+ERL_NIF_TERM rsa_opts_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ unsigned int cnt =
+#ifdef FIPS_SUPPORT
+ FIPS_mode() ? algo_rsa_opts_fips_cnt :
#endif
+ algo_rsa_opts_cnt;
+ return enif_make_list_from_array(env, algo_rsa_opts, cnt);
+}
+
+void init_rsa_opts_types(ErlNifEnv* env) {
// Validated algorithms first
algo_rsa_opts_cnt = 0;
#ifdef HAS_EVP_PKEY_CTX
@@ -235,68 +682,6 @@ void init_algorithms_types(ErlNifEnv* env)
algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_no_padding");
algo_rsa_opts_fips_cnt = algo_rsa_opts_cnt;
-
- // Check that the max number of algos is updated
- ASSERT(algo_hash_cnt <= sizeof(algo_hash)/sizeof(ERL_NIF_TERM));
- ASSERT(algo_pubkey_cnt <= sizeof(algo_pubkey)/sizeof(ERL_NIF_TERM));
- ASSERT(algo_curve_cnt <= sizeof(algo_curve)/sizeof(ERL_NIF_TERM));
ASSERT(algo_rsa_opts_cnt <= sizeof(algo_rsa_opts)/sizeof(ERL_NIF_TERM));
}
-
-ERL_NIF_TERM hash_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{
- unsigned int cnt =
-#ifdef FIPS_SUPPORT
- FIPS_mode() ? algo_hash_fips_cnt :
-#endif
- algo_hash_cnt;
-
- return enif_make_list_from_array(env, algo_hash, cnt);
-}
-
-ERL_NIF_TERM pubkey_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{
- unsigned int cnt =
-#ifdef FIPS_SUPPORT
- FIPS_mode() ? algo_pubkey_fips_cnt :
-#endif
- algo_pubkey_cnt;
-
- return enif_make_list_from_array(env, algo_pubkey, cnt);
-}
-
-
-ERL_NIF_TERM cipher_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{
- return cipher_types_as_list(env); /* Exclude old api ciphers */
-}
-
-
-ERL_NIF_TERM mac_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{
- return mac_types_as_list(env);
-}
-
-ERL_NIF_TERM curve_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{
- unsigned int cnt =
-#ifdef FIPS_SUPPORT
- FIPS_mode() ? algo_curve_fips_cnt :
-#endif
- algo_curve_cnt;
-
- return enif_make_list_from_array(env, algo_curve, cnt);
-}
-
-
-ERL_NIF_TERM rsa_opts_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{
- unsigned int cnt =
-#ifdef FIPS_SUPPORT
- FIPS_mode() ? algo_rsa_opts_fips_cnt :
-#endif
- algo_rsa_opts_cnt;
-
- return enif_make_list_from_array(env, algo_rsa_opts, cnt);
-}
diff --git a/lib/crypto/c_src/cipher.c b/lib/crypto/c_src/cipher.c
index 3db2258d5e..f9fac1761a 100644
--- a/lib/crypto/c_src/cipher.c
+++ b/lib/crypto/c_src/cipher.c
@@ -26,72 +26,74 @@
#define COND_NO_DES_PTR(Ptr) (NULL)
#endif
+#define NOT_AEAD {{0,0,0}}
+
static struct cipher_type_t cipher_types[] =
{
#ifdef HAVE_RC2
- {{"rc2_cbc"}, {&EVP_rc2_cbc}, 0, NO_FIPS_CIPHER},
+ {{"rc2_cbc"}, {&EVP_rc2_cbc}, 0, NO_FIPS_CIPHER, NOT_AEAD},
#else
- {{"rc2_cbc"}, {NULL}, 0, NO_FIPS_CIPHER},
+ {{"rc2_cbc"}, {NULL}, 0, NO_FIPS_CIPHER, NOT_AEAD},
#endif
#ifdef HAVE_RC4
- {{"rc4"}, {&EVP_rc4}, 0, NO_FIPS_CIPHER},
+ {{"rc4"}, {&EVP_rc4}, 0, NO_FIPS_CIPHER, NOT_AEAD},
#else
- {{"rc4"}, {NULL}, 0, NO_FIPS_CIPHER},
+ {{"rc4"}, {NULL}, 0, NO_FIPS_CIPHER, NOT_AEAD},
#endif
- {{"des_cbc"}, {COND_NO_DES_PTR(&EVP_des_cbc)}, 0, NO_FIPS_CIPHER},
- {{"des_cfb"}, {COND_NO_DES_PTR(&EVP_des_cfb8)}, 0, NO_FIPS_CIPHER},
- {{"des_ecb"}, {COND_NO_DES_PTR(&EVP_des_ecb)}, 0, NO_FIPS_CIPHER | ECB_BUG_0_9_8L},
+ {{"des_cbc"}, {COND_NO_DES_PTR(&EVP_des_cbc)}, 0, NO_FIPS_CIPHER, NOT_AEAD},
+ {{"des_cfb"}, {COND_NO_DES_PTR(&EVP_des_cfb8)}, 0, NO_FIPS_CIPHER, NOT_AEAD},
+ {{"des_ecb"}, {COND_NO_DES_PTR(&EVP_des_ecb)}, 0, NO_FIPS_CIPHER | ECB_BUG_0_9_8L, NOT_AEAD},
- {{"des_ede3_cbc"}, {COND_NO_DES_PTR(&EVP_des_ede3_cbc)}, 0, 0},
+ {{"des_ede3_cbc"}, {COND_NO_DES_PTR(&EVP_des_ede3_cbc)}, 0, 0, NOT_AEAD},
#ifdef HAVE_DES_ede3_cfb_encrypt
- {{"des_ede3_cfb"}, {COND_NO_DES_PTR(&EVP_des_ede3_cfb8)}, 0, 0},
+ {{"des_ede3_cfb"}, {COND_NO_DES_PTR(&EVP_des_ede3_cfb8)}, 0, 0, NOT_AEAD},
#else
- {{"des_ede3_cfb"}, {NULL}, 0, 0},
+ {{"des_ede3_cfb"}, {NULL}, 0, 0, NOT_AEAD},
#endif
#ifdef HAVE_BF
- {{"blowfish_cbc"}, {&EVP_bf_cbc}, 0, NO_FIPS_CIPHER},
- {{"blowfish_cfb64"}, {&EVP_bf_cfb64}, 0, NO_FIPS_CIPHER},
- {{"blowfish_ofb64"}, {&EVP_bf_ofb}, 0, NO_FIPS_CIPHER},
- {{"blowfish_ecb"}, {&EVP_bf_ecb}, 0, NO_FIPS_CIPHER | ECB_BUG_0_9_8L},
+ {{"blowfish_cbc"}, {&EVP_bf_cbc}, 0, NO_FIPS_CIPHER, NOT_AEAD},
+ {{"blowfish_cfb64"}, {&EVP_bf_cfb64}, 0, NO_FIPS_CIPHER, NOT_AEAD},
+ {{"blowfish_ofb64"}, {&EVP_bf_ofb}, 0, NO_FIPS_CIPHER, NOT_AEAD},
+ {{"blowfish_ecb"}, {&EVP_bf_ecb}, 0, NO_FIPS_CIPHER | ECB_BUG_0_9_8L, NOT_AEAD},
#else
- {{"blowfish_cbc"}, {NULL}, 0, 0},
- {{"blowfish_cfb64"}, {NULL}, 0, 0},
- {{"blowfish_ofb64"}, {NULL}, 0, 0},
- {{"blowfish_ecb"}, {NULL}, 0, 0},
+ {{"blowfish_cbc"}, {NULL}, 0, 0, NOT_AEAD},
+ {{"blowfish_cfb64"}, {NULL}, 0, 0, NOT_AEAD},
+ {{"blowfish_ofb64"}, {NULL}, 0, 0, NOT_AEAD},
+ {{"blowfish_ecb"}, {NULL}, 0, 0, NOT_AEAD},
#endif
- {{"aes_128_cbc"}, {&EVP_aes_128_cbc}, 16, 0},
- {{"aes_192_cbc"}, {&EVP_aes_192_cbc}, 24, 0},
- {{"aes_256_cbc"}, {&EVP_aes_256_cbc}, 32, 0},
+ {{"aes_128_cbc"}, {&EVP_aes_128_cbc}, 16, 0, NOT_AEAD},
+ {{"aes_192_cbc"}, {&EVP_aes_192_cbc}, 24, 0, NOT_AEAD},
+ {{"aes_256_cbc"}, {&EVP_aes_256_cbc}, 32, 0, NOT_AEAD},
- {{"aes_128_cfb8"}, {&EVP_aes_128_cfb8}, 16, AES_CFBx},
- {{"aes_192_cfb8"}, {&EVP_aes_192_cfb8}, 24, AES_CFBx},
- {{"aes_256_cfb8"}, {&EVP_aes_256_cfb8}, 32, AES_CFBx},
+ {{"aes_128_cfb8"}, {&EVP_aes_128_cfb8}, 16, AES_CFBx, NOT_AEAD},
+ {{"aes_192_cfb8"}, {&EVP_aes_192_cfb8}, 24, AES_CFBx, NOT_AEAD},
+ {{"aes_256_cfb8"}, {&EVP_aes_256_cfb8}, 32, AES_CFBx, NOT_AEAD},
- {{"aes_128_cfb128"}, {&EVP_aes_128_cfb128}, 16, AES_CFBx},
- {{"aes_192_cfb128"}, {&EVP_aes_192_cfb128}, 24, AES_CFBx},
- {{"aes_256_cfb128"}, {&EVP_aes_256_cfb128}, 32, AES_CFBx},
+ {{"aes_128_cfb128"}, {&EVP_aes_128_cfb128}, 16, AES_CFBx, NOT_AEAD},
+ {{"aes_192_cfb128"}, {&EVP_aes_192_cfb128}, 24, AES_CFBx, NOT_AEAD},
+ {{"aes_256_cfb128"}, {&EVP_aes_256_cfb128}, 32, AES_CFBx, NOT_AEAD},
- {{"aes_128_ecb"}, {&EVP_aes_128_ecb}, 16, ECB_BUG_0_9_8L},
- {{"aes_192_ecb"}, {&EVP_aes_192_ecb}, 24, ECB_BUG_0_9_8L},
- {{"aes_256_ecb"}, {&EVP_aes_256_ecb}, 32, ECB_BUG_0_9_8L},
+ {{"aes_128_ecb"}, {&EVP_aes_128_ecb}, 16, ECB_BUG_0_9_8L, NOT_AEAD},
+ {{"aes_192_ecb"}, {&EVP_aes_192_ecb}, 24, ECB_BUG_0_9_8L, NOT_AEAD},
+ {{"aes_256_ecb"}, {&EVP_aes_256_ecb}, 32, ECB_BUG_0_9_8L, NOT_AEAD},
#if defined(HAVE_EVP_AES_CTR)
- {{"aes_128_ctr"}, {&EVP_aes_128_ctr}, 16, 0},
- {{"aes_192_ctr"}, {&EVP_aes_192_ctr}, 24, 0},
- {{"aes_256_ctr"}, {&EVP_aes_256_ctr}, 32, 0},
+ {{"aes_128_ctr"}, {&EVP_aes_128_ctr}, 16, 0, NOT_AEAD},
+ {{"aes_192_ctr"}, {&EVP_aes_192_ctr}, 24, 0, NOT_AEAD},
+ {{"aes_256_ctr"}, {&EVP_aes_256_ctr}, 32, 0, NOT_AEAD},
#else
- {{"aes_128_ctr"}, {NULL}, 16, AES_CTR_COMPAT},
- {{"aes_192_ctr"}, {NULL}, 24, AES_CTR_COMPAT},
- {{"aes_256_ctr"}, {NULL}, 32, AES_CTR_COMPAT},
+ {{"aes_128_ctr"}, {NULL}, 16, AES_CTR_COMPAT, NOT_AEAD},
+ {{"aes_192_ctr"}, {NULL}, 24, AES_CTR_COMPAT, NOT_AEAD},
+ {{"aes_256_ctr"}, {NULL}, 32, AES_CTR_COMPAT, NOT_AEAD},
#endif
#if defined(HAVE_CHACHA20)
- {{"chacha20"}, {&EVP_chacha20}, 32, NO_FIPS_CIPHER},
+ {{"chacha20"}, {&EVP_chacha20}, 32, NO_FIPS_CIPHER, NOT_AEAD},
#else
- {{"chacha20"}, {NULL}, 0, NO_FIPS_CIPHER},
+ {{"chacha20"}, {NULL}, 0, NO_FIPS_CIPHER, NOT_AEAD},
#endif
/*==== AEAD ciphers ====*/
@@ -123,12 +125,12 @@ static struct cipher_type_t cipher_types[] =
/*==== Specialy handled ciphers, only for inclusion in algorithm's list ====*/
#ifdef HAVE_AES_IGE
- {{"aes_ige256"}, {NULL}, 0, NO_FIPS_CIPHER | NON_EVP_CIPHER},
+ {{"aes_ige256"}, {NULL}, 0, NO_FIPS_CIPHER | NON_EVP_CIPHER, NOT_AEAD},
#endif
/*==== End of list ==== */
- {{NULL},{NULL},0,0}
+ {{NULL},{NULL},0,0,NOT_AEAD}
};
ErlNifResourceType* evp_cipher_ctx_rtype;
diff --git a/lib/crypto/c_src/crypto.c b/lib/crypto/c_src/crypto.c
index f740b19a94..b88413d873 100644
--- a/lib/crypto/c_src/crypto.c
+++ b/lib/crypto/c_src/crypto.c
@@ -250,6 +250,7 @@ static int initialize(ErlNifEnv* env, ERL_NIF_TERM load_info)
#if OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(1,1,0)
#ifdef OPENSSL_THREADS
if (nlocks > 0) {
+ CRYPTO_set_add_lock_callback(ccb->add_lock_function);
CRYPTO_set_locking_callback(ccb->locking_function);
CRYPTO_set_id_callback(ccb->id_function);
CRYPTO_set_dynlock_create_callback(ccb->dyn_create_function);
diff --git a/lib/crypto/c_src/crypto_callback.c b/lib/crypto/c_src/crypto_callback.c
index f9cbd525d0..0244952a65 100644
--- a/lib/crypto/c_src/crypto_callback.c
+++ b/lib/crypto/c_src/crypto_callback.c
@@ -133,6 +133,28 @@ static INLINE void locking(int mode, ErlNifRWLock* lock)
}
#if OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(1,1,0)
+
+/* TODO: there should be an enif_atomic32_add_return() */
+
+typedef int (*add_lock_function_t)(int *var, int incr, int type, const char *file, int line);
+
+#if defined(__GNUC__) && defined(__ATOMIC_ACQ_REL)
+static int add_lock_function(int *var, int incr, int type, const char *file, int line)
+{
+ return __atomic_add_fetch(var, incr, __ATOMIC_ACQ_REL);
+}
+
+static add_lock_function_t get_add_lock_function(void)
+{
+ return __atomic_always_lock_free(sizeof(int), NULL) ? add_lock_function : NULL;
+}
+#else
+static add_lock_function_t get_add_lock_function(void)
+{
+ return NULL;
+}
+#endif
+
static void locking_function(int mode, int n, const char *file, int line)
{
locking(mode, lock_vec[n]);
@@ -172,6 +194,7 @@ DLLEXPORT struct crypto_callbacks* get_crypto_callbacks(int nlocks)
#if OPENSSL_VERSION_NUMBER < 0x10100000
#ifdef OPENSSL_THREADS
+ NULL, /* add_lock_function, filled in below */
&locking_function,
&id_function,
&dyn_create_function,
@@ -184,6 +207,7 @@ DLLEXPORT struct crypto_callbacks* get_crypto_callbacks(int nlocks)
if (!is_initialized) {
#if OPENSSL_VERSION_NUMBER < 0x10100000
#ifdef OPENSSL_THREADS
+ the_struct.add_lock_function = get_add_lock_function();
if (nlocks > 0) {
int i;
diff --git a/lib/crypto/c_src/crypto_callback.h b/lib/crypto/c_src/crypto_callback.h
index f59165886b..d7f14ac1cd 100644
--- a/lib/crypto/c_src/crypto_callback.h
+++ b/lib/crypto/c_src/crypto_callback.h
@@ -36,6 +36,8 @@ struct crypto_callbacks
/* openssl callbacks */
#if OPENSSL_VERSION_NUMBER < 0x10100000
#ifdef OPENSSL_THREADS
+ int (*add_lock_function)(int *num, int amount, int type,
+ const char *file, int line);
void (*locking_function)(int mode, int n, const char *file, int line);
unsigned long (*id_function)(void);
struct CRYPTO_dynlock_value* (*dyn_create_function)(const char *file,
diff --git a/lib/crypto/c_src/hmac.c b/lib/crypto/c_src/hmac.c
index 3b3e0bf894..3ea7c1f5dc 100644
--- a/lib/crypto/c_src/hmac.c
+++ b/lib/crypto/c_src/hmac.c
@@ -28,7 +28,7 @@
*
****************************************************************/
-#ifndef HAS_EVP_PKEY_CTX
+#if !defined(HAS_EVP_PKEY_CTX) || DISABLE_EVP_HMAC
#include "hmac.h"
#include "digest.h"
@@ -233,18 +233,19 @@ int hmac_low_level(ErlNifEnv* env, const EVP_MD *md,
{
unsigned int size_int;
size_t size;
+ unsigned char buff[EVP_MAX_MD_SIZE];
- /* Find the needed space */
if (HMAC(md,
key_bin.data, (int)key_bin.size,
text.data, text.size,
- NULL, &size_int) == NULL)
+ buff, &size_int) == NULL)
{
- *return_term = EXCP_ERROR(env, "Get HMAC size failed");
+ *return_term = EXCP_ERROR(env, "HMAC sign failed");
return 0;
}
size = (size_t)size_int; /* Otherwise "size" is unused in 0.9.8.... */
+ ASSERT(0 < size && size <= EVP_MAX_MD_SIZE);
if (!enif_alloc_binary(size, ret_bin))
{
*return_term = EXCP_ERROR(env, "Alloc binary");
@@ -252,15 +253,7 @@ int hmac_low_level(ErlNifEnv* env, const EVP_MD *md,
}
*ret_bin_alloc = 1;
- /* And do the real HMAC calc */
- if (HMAC(md,
- key_bin.data, (int)key_bin.size,
- text.data, text.size,
- ret_bin->data, &size_int) == NULL)
- {
- *return_term = EXCP_ERROR(env, "HMAC sign failed");
- return 0;
- }
+ memcpy(ret_bin->data, buff, size);
return 1;
}
diff --git a/lib/crypto/c_src/hmac.h b/lib/crypto/c_src/hmac.h
index deeab168a9..df432512f3 100644
--- a/lib/crypto/c_src/hmac.h
+++ b/lib/crypto/c_src/hmac.h
@@ -21,7 +21,7 @@
#ifndef E_HMAC_H__
#define E_HMAC_H__ 1
-#ifndef HAS_EVP_PKEY_CTX
+#if !defined(HAS_EVP_PKEY_CTX) || DISABLE_EVP_HMAC
#include "common.h"
diff --git a/lib/crypto/c_src/mac.c b/lib/crypto/c_src/mac.c
index 149975ba9d..8735158a01 100644
--- a/lib/crypto/c_src/mac.c
+++ b/lib/crypto/c_src/mac.c
@@ -62,7 +62,7 @@ static struct mac_type_t mac_types[] =
},
{{"hmac"}, 0,
-#ifdef HAS_EVP_PKEY_CTX
+#if defined(HAS_EVP_PKEY_CTX) && (! DISABLE_EVP_HMAC)
{EVP_PKEY_HMAC}, HMAC_mac, 0
#else
/* HMAC is always supported, but possibly with low-level routines */
@@ -271,7 +271,7 @@ ERL_NIF_TERM mac_one_time(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
}
md = digp->md.p;
-#ifdef HAS_EVP_PKEY_CTX
+#if defined(HAS_EVP_PKEY_CTX) && (! DISABLE_EVP_HMAC)
# ifdef HAVE_PKEY_new_raw_private_key
/* Prefered for new applications according to EVP_PKEY_new_mac_key(3) */
pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_HMAC, /*engine*/ NULL, key_bin.data, key_bin.size);
diff --git a/lib/crypto/c_src/openssl_config.h b/lib/crypto/c_src/openssl_config.h
index 7a04031cdb..cf63bd6051 100644
--- a/lib/crypto/c_src/openssl_config.h
+++ b/lib/crypto/c_src/openssl_config.h
@@ -109,14 +109,6 @@
#ifndef HAS_LIBRESSL
# if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
# define HAS_EVP_PKEY_CTX
-# if OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(1,0,2) \
- && (! DISABLE_EVP_DH)
- /* Diffie-Hellman EVP is slow on antique crypto libs
- * DISABLE_EVP_DH is 0 or 1 from the configure script
- */
-# undef DISABLE_EVP_DH
-# define DISABLE_EVP_DH 1
-# endif
# define HAVE_EVP_CIPHER_CTX_COPY
# endif
@@ -127,6 +119,16 @@
# endif
#endif
+#if defined(HAS_EVP_PKEY_CTX) \
+ && OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(1,0,2)
+ /* EVP is slow on antique crypto libs.
+ * DISABLE_EVP_* is 0 or 1 from the configure script
+ */
+# undef DISABLE_EVP_DH
+# define DISABLE_EVP_DH 1
+# undef DISABLE_EVP_HMAC
+# define DISABLE_EVP_HMAC 1
+#endif
#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
#include <openssl/modes.h>
diff --git a/lib/crypto/c_src/otp_test_engine.c b/lib/crypto/c_src/otp_test_engine.c
index 4963ee5f0b..6416925b05 100644
--- a/lib/crypto/c_src/otp_test_engine.c
+++ b/lib/crypto/c_src/otp_test_engine.c
@@ -203,7 +203,7 @@ static int test_engine_digest_selector(ENGINE *e, const EVP_MD **digest,
if (!digest) {
*nids = test_digest_ids;
fprintf(stderr, "Digest is empty! Nid:%d\r\n", nid);
- return 2;
+ return sizeof(test_digest_ids) / sizeof(*test_digest_ids);
}
fprintf(stderr, "Digest no %d requested\r\n",nid);
if (nid == NID_md5) {
diff --git a/lib/crypto/c_src/pkey.c b/lib/crypto/c_src/pkey.c
index d70db8570d..2125aed537 100644
--- a/lib/crypto/c_src/pkey.c
+++ b/lib/crypto/c_src/pkey.c
@@ -187,6 +187,10 @@ static int get_pkey_sign_options(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF
opt->rsa_mgf1_md = NULL;
opt->rsa_padding = RSA_PKCS1_PADDING;
opt->rsa_pss_saltlen = -2;
+ } else {
+ opt->rsa_mgf1_md = NULL;
+ opt->rsa_padding = 0;
+ opt->rsa_pss_saltlen = 0;
}
if (enif_is_empty_list(env, options))
@@ -528,8 +532,8 @@ ERL_NIF_TERM pkey_sign_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
#endif
PKeySignOptions sig_opt;
ErlNifBinary sig_bin; /* signature */
- unsigned char *tbs; /* data to be signed */
- size_t tbslen;
+ unsigned char *tbs = NULL; /* data to be signed */
+ size_t tbslen = 0;
RSA *rsa = NULL;
#ifdef HAVE_DSA
DSA *dsa = NULL;
@@ -757,8 +761,8 @@ ERL_NIF_TERM pkey_verify_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]
#endif
PKeySignOptions sig_opt;
ErlNifBinary sig_bin; /* signature */
- unsigned char *tbs; /* data to be signed */
- size_t tbslen;
+ unsigned char *tbs = NULL; /* data to be signed */
+ size_t tbslen = 0;
ERL_NIF_TERM ret;
RSA *rsa = NULL;
#ifdef HAVE_DSA
@@ -934,12 +938,19 @@ static int get_pkey_crypt_options(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NI
/* defaults */
if (algorithm == atom_rsa) {
- opt->rsa_mgf1_md = NULL;
- opt->rsa_oaep_label.data = NULL;
- opt->rsa_oaep_label.size = 0;
- opt->rsa_oaep_md = NULL;
+ opt->rsa_mgf1_md = NULL;
+ opt->rsa_oaep_label.data = NULL;
+ opt->rsa_oaep_label.size = 0;
+ opt->rsa_oaep_md = NULL;
opt->rsa_padding = RSA_PKCS1_PADDING;
- opt->signature_md = NULL;
+ opt->signature_md = NULL;
+ } else {
+ opt->rsa_mgf1_md = NULL;
+ opt->rsa_oaep_label.data = NULL;
+ opt->rsa_oaep_label.size = 0;
+ opt->rsa_oaep_md = NULL;
+ opt->rsa_padding = 0;
+ opt->signature_md = NULL;
}
if (enif_is_empty_list(env, options))
diff --git a/lib/crypto/c_src/srp.c b/lib/crypto/c_src/srp.c
index 2afd05d7b5..22fbedb4eb 100644
--- a/lib/crypto/c_src/srp.c
+++ b/lib/crypto/c_src/srp.c
@@ -57,6 +57,7 @@ ERL_NIF_TERM srp_value_B_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]
goto err;
/* g^b % N */
+ BN_set_flags(bn_exponent, BN_FLG_CONSTTIME);
if (!BN_mod_exp(bn_result, bn_generator, bn_exponent, bn_prime, bn_ctx))
goto err;
@@ -154,6 +155,7 @@ ERL_NIF_TERM srp_user_secret_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM ar
/* (B - (k * g^x)) */
if ((bn_base = BN_new()) == NULL)
goto err;
+ BN_set_flags(bn_exponent, BN_FLG_CONSTTIME);
if (!BN_mod_exp(bn_result, bn_generator, bn_exponent, bn_prime, bn_ctx))
goto err;
if (!BN_mod_mul(bn_result, bn_multiplier, bn_result, bn_prime, bn_ctx))
@@ -170,6 +172,7 @@ ERL_NIF_TERM srp_user_secret_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM ar
goto err;
/* (B - (k * g^x)) ^ (a + (u * x)) % N */
+ BN_set_flags(bn_exp2, BN_FLG_CONSTTIME);
if (!BN_mod_exp(bn_result, bn_base, bn_exp2, bn_prime, bn_ctx))
goto err;
@@ -258,12 +261,14 @@ ERL_NIF_TERM srp_host_secret_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM ar
/* (A * v^u) */
if ((bn_base = BN_new()) == NULL)
goto err;
+ BN_set_flags(bn_u, BN_FLG_CONSTTIME);
if (!BN_mod_exp(bn_base, bn_verifier, bn_u, bn_prime, bn_ctx))
goto err;
if (!BN_mod_mul(bn_base, bn_A, bn_base, bn_prime, bn_ctx))
goto err;
/* (A * v^u) ^ b % N */
+ BN_set_flags(bn_b, BN_FLG_CONSTTIME);
if (!BN_mod_exp(bn_result, bn_base, bn_b, bn_prime, bn_ctx))
goto err;
diff --git a/lib/crypto/configure.in b/lib/crypto/configure.in
index 71324c0685..acab542cda 100644
--- a/lib/crypto/configure.in
+++ b/lib/crypto/configure.in
@@ -25,12 +25,7 @@ dnl define([AC_CACHE_SAVE], )dnl
AC_INIT(vsn.mk)
-if test -z "$ERL_TOP" || test ! -d "$ERL_TOP" ; then
- AC_CONFIG_AUX_DIRS(autoconf)
-else
- erl_top=${ERL_TOP}
- AC_CONFIG_AUX_DIRS($erl_top/erts/autoconf)
-fi
+AC_CONFIG_AUX_DIRS(${ERL_TOP}/erts/autoconf)
if test "X$host" != "Xfree_source" -a "X$host" != "Xwin32"; then
AC_CANONICAL_HOST
@@ -186,6 +181,15 @@ AS_HELP_STRING([--disable-evp-dh],
esac ], DISABLE_EVP_DH=0)
+AC_ARG_ENABLE(evp-hmac,
+AS_HELP_STRING([--disable-evp-hmac],
+ [intentionally undocumented workaround]),
+[ case "$enableval" in
+ no) DISABLE_EVP_HMAC=1;;
+ *) DISABLE_EVP_HMAC=0;;
+ esac ], DISABLE_EVP_HMAC=0)
+
+
#----------------------------------------------------------------------
# We actually might do the SSL tests twice due to late discovery of
# kerberos problems with static linking, in case we redo it all trying
@@ -788,6 +792,7 @@ AC_SUBST(STATIC_KERBEROS_LIBS)
AC_SUBST(SSL_LINK_WITH_ZLIB)
AC_SUBST(STATIC_ZLIB_LIBS)
AC_SUBST(DISABLE_EVP_DH)
+AC_SUBST(DISABLE_EVP_HMAC)
AC_OUTPUT(c_src/$host/Makefile:c_src/Makefile.in)
diff --git a/lib/crypto/doc/src/Makefile b/lib/crypto/doc/src/Makefile
index 5460129c64..f48a79e8d1 100644
--- a/lib/crypto/doc/src/Makefile
+++ b/lib/crypto/doc/src/Makefile
@@ -47,3 +47,5 @@ IMAGE_FILES =
TOP_SPECS_FILE = specs.xml
include $(ERL_TOP)/make/doc.mk
+
+valgrind:
diff --git a/lib/crypto/doc/src/crypto.xml b/lib/crypto/doc/src/crypto.xml
index 86e3f0158a..e0b190a508 100644
--- a/lib/crypto/doc/src/crypto.xml
+++ b/lib/crypto/doc/src/crypto.xml
@@ -434,9 +434,10 @@
<code>rsa_private() = [E, N, D] | [E, N, D, P1, P2, E1, E2, C]</code>
<p>Where E is the public exponent, N is public modulus and D is
the private exponent. The longer key format contains redundant
- information that will make the calculation faster. P1,P2 are first
- and second prime factors. E1,E2 are first and second exponents. C
- is the CRT coefficient. Terminology is taken from <url href="http://www.ietf.org/rfc/rfc3477.txt"> RFC 3447</url>.</p>
+ information that will make the calculation faster. P1 and P2 are first
+ and second prime factors. E1 and E2 are first and second exponents. C
+ is the CRT coefficient. The terminology is taken from
+ <url href="http://www.ietf.org/rfc/rfc3447.txt"> RFC 3447</url>.</p>
</desc>
</datatype>
diff --git a/lib/crypto/doc/src/notes.xml b/lib/crypto/doc/src/notes.xml
index b2b7cfb7ee..d2b65cd275 100644
--- a/lib/crypto/doc/src/notes.xml
+++ b/lib/crypto/doc/src/notes.xml
@@ -31,6 +31,82 @@
</header>
<p>This document describes the changes made to the Crypto application.</p>
+<section><title>Crypto 4.8.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Adding missing flag in BN-calls in SRP.</p>
+ <p>
+ Own Id: OTP-17107</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Crypto 4.8.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed usage of <c>AC_CONFIG_AUX_DIRS()</c> macros in
+ configure script sources.</p>
+ <p>
+ Own Id: OTP-17093 Aux Id: ERL-1447, PR-2948 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Crypto 4.8.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Build the supported curves cache in the NIF when crypto
+ is loaded, no matter how it is loaded.</p>
+ <p>
+ This prevents a possible problem with different processes
+ starting the crypto application concurrently.</p>
+ <p>
+ Own Id: OTP-16819 Aux Id: PR-2720 </p>
+ </item>
+ <item>
+ <p>
+ It is now possible to build with crypto and openssl
+ gprof-enabled and statically link them into the VM.</p>
+ <p>
+ Own Id: OTP-17029</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Fixed performance loss in HMAC when using older OpenSSL
+ due to mutex issues.</p>
+ <p>
+ A workaround is implemented to allow fallback from using
+ the EVP API for HMAC operations. On some architectures
+ this may improve the performance, especially with old
+ OpenSSL versions. This fallback to low-level functions is
+ always enabled for openssl versions before 1.0.2.</p>
+ <p>
+ Own Id: OTP-17025 Aux Id: ERL-1400, PR-2877 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Crypto 4.8</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/crypto/src/crypto.erl b/lib/crypto/src/crypto.erl
index d43a94031d..d178ecc28e 100644
--- a/lib/crypto/src/crypto.erl
+++ b/lib/crypto/src/crypto.erl
@@ -646,13 +646,7 @@ version() -> ?CRYPTO_VSN.
-spec start() -> ok | {error, Reason::term()}.
start() ->
- case application:start(crypto) of
- ok ->
- _ = supports(curves), % Build curves cache if needed
- ok;
- Error ->
- Error
- end.
+ application:start(crypto).
-spec stop() -> ok | {error, Reason::term()}.
stop() ->
@@ -708,7 +702,7 @@ supports(hashs) -> hash_algorithms();
supports(public_keys) -> pubkey_algorithms();
supports(ciphers) -> cipher_algorithms();
supports(macs) -> mac_algorithms();
-supports(curves) -> cached_curve_algorithms();
+supports(curves) -> curve_algorithms();
supports(rsa_opts) -> rsa_opts_algorithms().
@@ -724,47 +718,7 @@ info_fips() -> ?nif_stub.
-spec enable_fips_mode(Enable) -> Result when Enable :: boolean(),
Result :: boolean().
enable_fips_mode(Enable) ->
- OldState = info_fips(),
- Result = enable_fips_mode_nif(Enable),
- case info_fips() of
- OldState ->
- %% No state change, so no need to touch the curve's cache
- Result;
- NewState ->
- %% State change (not_enabled -> enabled or enabled -> not_enabled)
- NewCurves =
- case application:get_env(?MODULE, var_name(NewState)) of
- {ok,Cs} when is_list(Cs) ->
- %% We have been in this state before, and saved the
- %% list for that state.
- Cs;
- _ ->
- %% We have not been in this state before. Rebuild and save.
- %% But first maintain the local enable_fips_mode_nif cache:
- case application:get_env(?MODULE, var_name(OldState)) of
- undefined ->
- %% Make the next state change fast:
- OldCs = application:get_env(?MODULE,?CURVES),
- application:set_env(?MODULE, var_name(OldState), OldCs);
- _ ->
- ok
- end,
- %% Now rebuild the list by hard work:
- application:unset_env(?MODULE, ?CURVES), % This will force a re-build
- %% Re-build curves cache. Not strictly needed here.
- Cs = supports(curves),
- %% We came here because var(NewState) wasn't set, so make the
- %% next state change fast:
- application:set_env(?MODULE, var_name(NewState), Cs),
- Cs
- end,
- application:set_env(?MODULE, ?CURVES, NewCurves), % So the call to supports(curves) will be fast
- Result
- end.
-
-var_name(enabled) -> '$curves-fips-enabled$';
-var_name(not_enabled) -> '$curves-fips-not_enabled$'.
-
+ enable_fips_mode_nif(Enable).
enable_fips_mode_nif(_) -> ?nif_stub.
@@ -2837,26 +2791,6 @@ exor(Data1, Data2, _Size, MaxByts, Acc) ->
do_exor(_A, _B) -> ?nif_stub.
-cached_curve_algorithms() ->
- case application:get_env(?MODULE, ?CURVES) of
- undefined ->
- Cs = remove_unavailable_curves(curve_algorithms()),
- application:set_env(?MODULE, ?CURVES, Cs),
- Cs;
- {ok,Cs} ->
- Cs
- end.
-
-remove_unavailable_curves(Cs) ->
- [C || C <- Cs,
- lists:member(C,[ed25519,ed448,x25519,x448])
- orelse try
- crypto:generate_key(ecdh,C)
- of _-> true
- catch _:_-> false
- end
- ].
-
hash_algorithms() -> ?nif_stub.
pubkey_algorithms() -> ?nif_stub.
cipher_algorithms() -> ?nif_stub.
diff --git a/lib/crypto/test/crypto_SUITE.erl b/lib/crypto/test/crypto_SUITE.erl
index 36afa22926..f7b7c39099 100644
--- a/lib/crypto/test/crypto_SUITE.erl
+++ b/lib/crypto/test/crypto_SUITE.erl
@@ -67,6 +67,8 @@
compute/1,
compute_bug/0,
compute_bug/1,
+ crypto_load/1,
+ crypto_load_and_call/1,
exor/0,
exor/1,
generate/0,
@@ -107,6 +109,7 @@
no_stream_ivec/1,
no_support/0,
no_support/1,
+ node_supports_cache/1,
poly1305/0,
poly1305/1,
private_encrypt/0,
@@ -214,9 +217,12 @@ all() ->
[app,
{group, api_errors},
appup,
+ crypto_load,
+ crypto_load_and_call,
{group, fips},
{group, non_fips},
cipher_padding,
+ node_supports_cache,
mod_pow,
exor,
rand_uniform,
@@ -620,6 +626,58 @@ no_support(Config) when is_list(Config) ->
Type = ?config(type, Config),
false = is_supported(Type).
%%--------------------------------------------------------------------
+crypto_load(_Config) ->
+ (catch crypto:stop()),
+ code:delete(crypto),
+ code:purge(crypto),
+ crypto:start().
+%%--------------------------------------------------------------------
+crypto_load_and_call(_Config) ->
+ (catch crypto:stop()),
+ code:delete(crypto),
+ code:purge(crypto),
+ Key0 = "ablurf123BX#$;3",
+ Bin0 = erlang:md5(<<"whatever">>),
+ {Key,IVec,BlockSize}=make_crypto_key(Key0),
+ crypto:crypto_one_time(des_ede3_cbc, Key, IVec, Bin0, true).
+
+make_crypto_key(String) ->
+ <<K1:8/binary,K2:8/binary>> = First = erlang:md5(String),
+ <<K3:8/binary,IVec:8/binary>> = erlang:md5([First|lists:reverse(String)]),
+ {[K1,K2,K3],IVec,8}.
+%%--------------------------------------------------------------------
+%% Test that a spawned node has initialized the cache
+-define(at_node,
+ (fun(N, M, F, As) ->
+ R = rpc:call(N, M, F, As),
+ ct:log("~p ~p ~p:~p(~s) = ~p", [?LINE,N,M,F,args2list(As), R]),
+ R
+ end) ).
+args2list(As) -> lists:join(", ", [io_lib:format("~p",[A]) || A <- As]).
+
+node_supports_cache(_Config) ->
+ ECs = crypto:supports(curves),
+ {ok,Node} = start_slave_node(random_node_name(?MODULE)),
+ case ?at_node(Node, crypto, supports, [curves]) of
+ ECs ->
+ test_server:stop_node(Node);
+ OtherECs ->
+ ct:log("At master:~p~nAt slave:~p~n"
+ "Missing at slave: ~p~nmissing at master: ~p",
+ [ECs, OtherECs, ECs--OtherECs, OtherECs--ECs]),
+ {fail, "different support at slave"}
+ end.
+
+
+start_slave_node(Name) ->
+ Pa = filename:dirname(code:which(?MODULE)),
+ test_server:start_node(Name, slave, [{args, " -pa " ++ Pa}]).
+
+random_node_name(BaseName) ->
+ L = integer_to_list(erlang:unique_integer([positive])),
+ lists:concat([BaseName,"___",L]).
+
+%%--------------------------------------------------------------------
hash() ->
[{doc, "Test all different hash functions"}].
hash(Config) when is_list(Config) ->
diff --git a/lib/crypto/vsn.mk b/lib/crypto/vsn.mk
index 38a016a0bf..0fb42b3993 100644
--- a/lib/crypto/vsn.mk
+++ b/lib/crypto/vsn.mk
@@ -1 +1 @@
-CRYPTO_VSN = 4.8
+CRYPTO_VSN = 4.8.3
diff --git a/lib/debugger/doc/src/int.xml b/lib/debugger/doc/src/int.xml
index a2fced44c2..d0706f2aa2 100644
--- a/lib/debugger/doc/src/int.xml
+++ b/lib/debugger/doc/src/int.xml
@@ -316,7 +316,7 @@ spawn(Module, Name, [Pid | Args])</pre>
tail recursive calls. This is the default.</p></item>
<tag><c>false</c></tag>
- <item><p>Save no information about currentcalls.</p></item>
+ <item><p>Save no information about current calls.</p></item>
</taglist>
</desc>
</func>
diff --git a/lib/dialyzer/doc/src/dialyzer.xml b/lib/dialyzer/doc/src/dialyzer.xml
index 91d7831383..5dd40a595b 100644
--- a/lib/dialyzer/doc/src/dialyzer.xml
+++ b/lib/dialyzer/doc/src/dialyzer.xml
@@ -391,7 +391,9 @@ dialyzer --plts plt_1 ... plt_n -- files_to_analyze</code>
<item>
<p>Include warnings for function calls that ignore a structured return
value or do not match against one of many possible return
- value(s).</p>
+ values. However, no warnings are included if the possible return
+ values are a union of atoms or a union of numbers.
+ </p>
</item>
</taglist>
diff --git a/lib/dialyzer/doc/src/notes.xml b/lib/dialyzer/doc/src/notes.xml
index fcc08a0fc3..7667ab9459 100644
--- a/lib/dialyzer/doc/src/notes.xml
+++ b/lib/dialyzer/doc/src/notes.xml
@@ -32,6 +32,22 @@
<p>This document describes the changes made to the Dialyzer
application.</p>
+<section><title>Dialyzer 4.3</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Clarify warning option <c>-Wunmatched_returns</c> in
+ <c>dialyzer(3)</c>.</p>
+ <p>
+ Own Id: OTP-17068 Aux Id: ERL-1223 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Dialyzer 4.2.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/dialyzer/vsn.mk b/lib/dialyzer/vsn.mk
index d1e94440fe..3e7cebb1c9 100644
--- a/lib/dialyzer/vsn.mk
+++ b/lib/dialyzer/vsn.mk
@@ -1 +1 @@
-DIALYZER_VSN = 4.2.1
+DIALYZER_VSN = 4.3
diff --git a/lib/erl_docgen/doc/src/doc-build.xml b/lib/erl_docgen/doc/src/doc-build.xml
index 17e13bff81..ee5bd260da 100644
--- a/lib/erl_docgen/doc/src/doc-build.xml
+++ b/lib/erl_docgen/doc/src/doc-build.xml
@@ -43,7 +43,7 @@
</p>
<code>
- 1> escript $(ERL_TOP)/lib/erl_docgen/priv/bin/xml_from_edoc.escript ex1.erl
+ 1> escript $ERL_TOP/lib/erl_docgen/priv/bin/xml_from_edoc.escript ex1.erl
</code>
</section>
<section>
@@ -57,7 +57,7 @@
</p>
<code>
- 1> escript $(ERL_TOP)/lib/erl_docgen/priv/bin/codeline_preprocessing.escript \
+ 1> escript $ERL_TOP/lib/erl_docgen/priv/bin/codeline_preprocessing.escript \
ex1.xmlsrc ex1.xml
</code>
</section>
@@ -116,9 +116,9 @@
<code>
1> xsltproc --noout --stringparam outdir /tmp/myhtmldoc \
- --stringparam docgen $(ERL_TOP)/lib/erl_docgen \
+ --stringparam docgen $ERL_TOP/lib/erl_docgen \
--stringparam topdocdir . \
- --stringparam pdfdir "$(PDFDIR)" \
+ --stringparam pdfdir $PDFDIR \
--xinclude \
--stringparam gendate "December 5 2011" \
--stringparam appname MyApp \
diff --git a/lib/erl_docgen/doc/src/notes.xml b/lib/erl_docgen/doc/src/notes.xml
index bc236ac9e9..603fcdf1be 100644
--- a/lib/erl_docgen/doc/src/notes.xml
+++ b/lib/erl_docgen/doc/src/notes.xml
@@ -31,7 +31,29 @@
</header>
<p>This document describes the changes made to the <em>erl_docgen</em> application.</p>
- <section><title>Erl_Docgen 1.0.1</title>
+ <section><title>Erl_Docgen 1.0.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix links in titles to github and anchors to work.</p>
+ <p>
+ Own Id: OTP-17013</p>
+ </item>
+ <item>
+ <p>
+ Fix some typing errors on variable names in documentation
+ examples.</p>
+ <p>
+ Own Id: OTP-17065 Aux Id: ERL-1386 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erl_Docgen 1.0.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/erl_docgen/priv/css/otp_doc.css b/lib/erl_docgen/priv/css/otp_doc.css
index 8ac02dcd96..d3e26f30e5 100644
--- a/lib/erl_docgen/priv/css/otp_doc.css
+++ b/lib/erl_docgen/priv/css/otp_doc.css
@@ -294,22 +294,47 @@ th {
outline: none;
}
-.ghlink {
- margin-left: -2.7em; /* .pencil.font-size + .pencil.padding.left + .pencil.padding.right = 2.7 */
+.ghlink-before {
+ margin-left: -4em;
visibility: hidden;
}
-.pencil:before {
+.pencil-before:before {
transform: rotateZ(90deg);
+ display: inline-block;
content: "\270E";
color: #1a1a1a !important;
- font-weight: bold;
font-size: 1.5em;
- padding: .3em .6em .6em;
- line-height: 1em;
font-family: mono;
}
+.paperclip-before:before {
+ display: inline-block;
+ content: "\1F517";
+ padding-left: 1em;
+ padding-right: .3em;
+}
+
+.ghlink-after {
+ visibility: hidden;
+}
+
+.pencil-after:after {
+ display: inline-block;
+ transform: rotate(90deg);
+ content: "\270E";
+ color: #1a1a1a !important;
+ font-size: 1.5em;
+ font-family: mono;
+}
+
+.paperclip-after:after {
+ display: inline-block;
+ content: "\1F517";
+ padding-right: .3em;
+ padding-left: .7em;
+}
+
hr{
border: 0;
border-top: 1px solid #aaa;
diff --git a/lib/erl_docgen/priv/xsl/db_html.xsl b/lib/erl_docgen/priv/xsl/db_html.xsl
index f495bdd371..690aa592c7 100644
--- a/lib/erl_docgen/priv/xsl/db_html.xsl
+++ b/lib/erl_docgen/priv/xsl/db_html.xsl
@@ -54,6 +54,27 @@
<func:result select="$result"/>
</func:function>
+ <!-- This is a XSLT 1.0 version of replace for string -->
+ <xsl:template name="string-replace-all">
+ <xsl:param name="text" />
+ <xsl:param name="replace" />
+ <xsl:param name="by" />
+ <xsl:choose>
+ <xsl:when test="contains($text, $replace)">
+ <xsl:value-of select="substring-before($text,$replace)" />
+ <xsl:value-of select="$by" />
+ <xsl:call-template name="string-replace-all">
+ <xsl:with-param name="text" select="substring-after($text,$replace)" />
+ <xsl:with-param name="replace" select="$replace" />
+ <xsl:with-param name="by" select="$by" />
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$text" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
<func:function name="erl:lower-case">
<xsl:param name="str"/>
@@ -227,10 +248,10 @@
<xsl:variable name="local_types"
select="../type[string-length(@name) > 0]"/>
<xsl:apply-templates select="$spec/contract/clause/head">
- <xsl:with-param name="ghlink" select="ancestor-or-self::*[@ghlink]/@ghlink"/>
- <xsl:with-param name="local_types" select="$local_types"/>
- <xsl:with-param name="global_types" select="$global_types"/>
- <xsl:with-param name="since" select="$since"/>
+ <xsl:with-param name="ghlink" select="ancestor-or-self::*[@ghlink]/@ghlink"/>
+ <xsl:with-param name="local_types" select="$local_types"/>
+ <xsl:with-param name="global_types" select="$global_types"/>
+ <xsl:with-param name="since" select="$since"/>
</xsl:apply-templates>
</xsl:when>
</xsl:choose>
@@ -241,7 +262,8 @@
<xsl:param name="local_types"/>
<xsl:param name="global_types"/>
<xsl:param name="since"/>
- <xsl:variable name="id" select="concat(concat(concat(concat(../../../name,'-'),../../../arity),'-'),generate-id(.))"/>
+ <xsl:variable name="mfa" select="concat(concat(../../../name,'-'),../../../arity)"/>
+ <xsl:variable name="id" select="concat(concat($mfa,'-'),generate-id(.))"/>
<table class="func-table">
<tr class="func-tr">
<td class="func-td">
@@ -249,6 +271,7 @@
onMouseOver="document.getElementById('ghlink-{$id}').style.visibility = 'visible';"
onMouseOut="document.getElementById('ghlink-{$id}').style.visibility = 'hidden';">
<xsl:call-template name="ghlink">
+ <xsl:with-param name="mfa" select="$mfa"/>
<xsl:with-param name="ghlink" select="$ghlink"/>
<xsl:with-param name="id" select="$id"/>
</xsl:call-template>
@@ -469,15 +492,32 @@
<div class="data-types-body">
<xsl:choose>
<xsl:when test="string-length(name/@name) > 0">
- <xsl:variable name="id" select="concat('type-',name/@name)"/>
- <div class="data-type-name"
- onMouseOver="document.getElementById('ghlink-{$id}').style.visibility = 'visible';"
- onMouseOut="document.getElementById('ghlink-{$id}').style.visibility = 'hidden';">
- <xsl:call-template name="ghlink">
- <xsl:with-param name="id" select="$id"/>
+ <xsl:variable name="apostrophe">'</xsl:variable>
+ <xsl:variable name="slash">/</xsl:variable>
+ <xsl:variable name="slash_encoded">%2f</xsl:variable>
+ <xsl:variable name="id">
+ <xsl:variable name="id-no-apostrophe">
+ <xsl:call-template name="string-replace-all">
+ <xsl:with-param name="text" select="concat('type-',name/@name)" />
+ <xsl:with-param name="replace" select="$apostrophe" />
+ <xsl:with-param name="by" select="''"/>
</xsl:call-template>
- <xsl:apply-templates select="name"/>
- </div>
+ </xsl:variable>
+ <xsl:call-template name="string-replace-all">
+ <xsl:with-param name="text" select="$id-no-apostrophe" />
+ <xsl:with-param name="replace" select="$slash" />
+ <xsl:with-param name="by" select="$slash_encoded" />
+ </xsl:call-template>
+ </xsl:variable>
+ <div class="data-type-name"
+ onMouseOver="document.getElementById('ghlink-{$id}').style.visibility = 'visible';"
+ onMouseOut="document.getElementById('ghlink-{$id}').style.visibility = 'hidden';">
+ <xsl:call-template name="ghlink">
+ <xsl:with-param name="mfa" select="$id"/>
+ <xsl:with-param name="id" select="$id"/>
+ </xsl:call-template>
+ <xsl:apply-templates select="name"/>
+ </div>
</xsl:when>
<xsl:otherwise>
<div class="data-type-name">
@@ -2278,6 +2318,7 @@
<span class="bold_code bc-7">
<xsl:call-template name="title_link">
<xsl:with-param name="link" select="substring-before(nametext, '(')"/>
+ <xsl:with-param name="where" select="'before'"/>
</xsl:call-template>
</span>
</td>
@@ -2311,6 +2352,7 @@
<div class="bold_code bc-8">
<xsl:call-template name="title_link">
<xsl:with-param name="link" select="concat('type-',$fname)"/>
+ <xsl:with-param name="where" select="'before'"/>
<xsl:with-param name="title">
<xsl:apply-templates/>
</xsl:with-param>
@@ -2324,6 +2366,7 @@
<div class="bold_code fun-type">
<xsl:call-template name="title_link">
<xsl:with-param name="link" select="concat(concat($fname,'-'),$arity)"/>
+ <xsl:with-param name="where" select="'before'"/>
<xsl:with-param name="title">
<xsl:apply-templates/>
</xsl:with-param>
@@ -2402,43 +2445,63 @@
<xsl:template name="title_link">
<xsl:param name="title" select="'APPLY'"/>
<xsl:param name="link" select="erl:to-link(title)"/>
+ <xsl:param name="where" select="'after'"/>
<xsl:param name="ghlink" select="ancestor-or-self::*[@ghlink][position() = 1]/@ghlink"/>
<xsl:variable name="id" select="concat(concat($link,'-'), generate-id(.))"/>
<span onMouseOver="document.getElementById('ghlink-{$id}').style.visibility = 'visible';"
onMouseOut="document.getElementById('ghlink-{$id}').style.visibility = 'hidden';">
- <xsl:call-template name="ghlink">
- <xsl:with-param name="id" select="$id"/>
- <xsl:with-param name="ghlink" select="$ghlink"/>
- </xsl:call-template>
- <a class="title_link" name="{$link}" href="#{$link}">
- <xsl:choose>
+ <xsl:choose>
+ <xsl:when test="$where = 'before'">
+ <xsl:call-template name="ghlink">
+ <xsl:with-param name="mfa" select="$link"/>
+ <xsl:with-param name="id" select="$id"/>
+ <xsl:with-param name="ghlink" select="$ghlink"/>
+ <xsl:with-param name="where" select="$where"/>
+ </xsl:call-template>
+ </xsl:when>
+ </xsl:choose>
+ <a class="title_link" name="{$link}">
+ <xsl:choose>
<xsl:when test="$title = 'APPLY'">
<xsl:apply-templates/> <!-- like <ret> and <nametext> -->
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$title"/>
</xsl:otherwise>
- </xsl:choose>
+ </xsl:choose>
</a>
+ <xsl:choose>
+ <xsl:when test="$where = 'after'">
+ <xsl:call-template name="ghlink">
+ <xsl:with-param name="mfa" select="$link"/>
+ <xsl:with-param name="id" select="$id"/>
+ <xsl:with-param name="ghlink" select="$ghlink"/>
+ <xsl:with-param name="where" select="$where"/>
+ </xsl:call-template>
+ </xsl:when>
+ </xsl:choose>
</span>
</xsl:template>
<xsl:template name="ghlink">
+ <xsl:param name="mfa"/>
<xsl:param name="id"/>
<xsl:param name="ghlink" select="ancestor-or-self::*[@ghlink][position() = 1]/@ghlink"/>
- <xsl:choose>
- <xsl:when test="string-length($ghlink) > 0">
- <span id="ghlink-{$id}" class="ghlink">
+ <xsl:param name="where" select="'before'"/>
+ <xsl:variable name="escaped_mfa" select="$mfa"/>
+ <span id="ghlink-{$id}" class="ghlink-{$where}">
+ <a href="#{$mfa}" title="Link to this place!">
+ <span class="paperclip-{$where}"/>
+ </a>
+ <xsl:choose>
+ <xsl:when test="string-length($ghlink) > 0">
<a href="https://github.com/erlang/otp/edit/{$ghlink}"
title="Found an issue with the documentation? Fix it by clicking here!">
- <span class="pencil"/>
+ <span class="pencil-{$where}"/>
</a>
- </span>
- </xsl:when>
- <xsl:otherwise>
- <span id="ghlink-{$id}"/>
- </xsl:otherwise>
- </xsl:choose>
+ </xsl:when>
+ </xsl:choose>
+ </span>
</xsl:template>
<!-- Desc -->
diff --git a/lib/erl_docgen/vsn.mk b/lib/erl_docgen/vsn.mk
index 41c61c2d7a..4b05826b61 100644
--- a/lib/erl_docgen/vsn.mk
+++ b/lib/erl_docgen/vsn.mk
@@ -1 +1 @@
-ERL_DOCGEN_VSN = 1.0.1
+ERL_DOCGEN_VSN = 1.0.2
diff --git a/lib/erl_interface/doc/src/notes.xml b/lib/erl_interface/doc/src/notes.xml
index 97f4460e1d..7b904b9ff5 100644
--- a/lib/erl_interface/doc/src/notes.xml
+++ b/lib/erl_interface/doc/src/notes.xml
@@ -31,6 +31,49 @@
</header>
<p>This document describes the changes made to the Erl_interface application.</p>
+<section><title>Erl_Interface 4.0.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Integers outside of the range [-(1 bsl 32) - 1, (1 bsl
+ 32) -1] were previously intended to be printed in an
+ internal bignum format by <c>ei_print_term()</c> and
+ <c>ei_s_print_term()</c>. Unfortunately the
+ implementation has been buggy since OTP R13B02 and since
+ then produced results with random content which also
+ could crash the calling program.</p>
+ <p>
+ This fix replaces the printing of the internal format
+ with printing in hexadecimal form and extend the range
+ for printing in decimal form. Currently integers in the
+ range [-(1 bsl 64), (1 bsl 64)] are printed in decimal
+ form and integers outside of this range in Erlang
+ hexadecimal form.</p>
+ <p>
+ Own Id: OTP-17099 Aux Id: ERIERL-585 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Known Bugs and Problems</title>
+ <list>
+ <item>
+ <p>
+ The <c>ei</c> API for decoding/encoding terms is not
+ fully 64-bit compatible since terms that have a
+ representation on the external term format larger than 2
+ GB cannot be handled.</p>
+ <p>
+ Own Id: OTP-16607 Aux Id: OTP-16608 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Erl_Interface 4.0.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/erl_interface/src/misc/ei_printterm.c b/lib/erl_interface/src/misc/ei_printterm.c
index d7d3c0e3e3..0bad730095 100644
--- a/lib/erl_interface/src/misc/ei_printterm.c
+++ b/lib/erl_interface/src/misc/ei_printterm.c
@@ -87,24 +87,73 @@ static char *ei_big_to_str(erlang_big *b)
{
int buf_len;
char *s,*buf;
+ unsigned int no_digits;
unsigned short *sp;
int i;
+ int printed;
- buf_len = 64+b->is_neg+10*b->arity;
- if ( (buf=malloc(buf_len)) == NULL) return NULL;
+ /* Number of 16-bit digits */
+ no_digits = (b->arity + 1) / 2;
+
+ if (no_digits <= 4) {
+ EI_ULONGLONG val;
+ buf_len = 22;
+ s = buf = malloc(buf_len);
+ if (!buf)
+ return NULL;
+ val = 0;
+ sp=b->digits;
+ for (i = 0; i < no_digits; i++)
+ val |= ((EI_ULONGLONG) sp[i]) << (i*16);
+ if (b->is_neg)
+ s += sprintf(s,"-");
+ sprintf(s, "%llu", val);
+ return buf;
+ }
- memset(buf,(char)0,buf_len);
+ /* big nums this large gets printed in base 16... */
+ buf_len = (!!b->is_neg /* "-" */
+ + 3 /* "16#" */
+ + 4*no_digits /* 16-bit digits in base 16 */
+ + 1); /* \0 */
+ if ( (buf=malloc(buf_len)) == NULL) return NULL;
s = buf;
if ( b->is_neg )
- s += sprintf(s,"-");
- s += sprintf(s,"#integer(%d) = {",b->arity);
- for(sp=b->digits,i=0;i<b->arity;i++) {
- s += sprintf(s,"%d",sp[i]);
- if ( (i+1) != b->arity )
- s += sprintf(s,",");
+ *(s++) = '-';
+ *(s++) = '1';
+ *(s++) = '6';
+ *(s++) = '#';
+
+ sp = b->digits;
+ printed = 0;
+ for (i = no_digits - 1; i >= 0; i--) {
+ unsigned short val = sp[i];
+ int j;
+
+ for (j = 3; j >= 0; j--) {
+ char c = (char) ((val >> (j*4)) & 0xf);
+ if (c < 10)
+ c += '0';
+ else
+ c += 'A' - 10;
+
+ if (printed)
+ *(s++) = c;
+ else if (c != '0') {
+ *(s++) = c;
+ printed = !0;
+ }
+ }
}
- s += sprintf(s,"}");
+
+ if (!printed) {
+ /* very strange to encode zero like this... */
+ *(s++) = '0';
+ }
+
+
+ *s = '\0';
return buf;
}
diff --git a/lib/erl_interface/test/all_SUITE_data/ei_runner.c b/lib/erl_interface/test/all_SUITE_data/ei_runner.c
index cd7a67c57c..56c88c46f2 100644
--- a/lib/erl_interface/test/all_SUITE_data/ei_runner.c
+++ b/lib/erl_interface/test/all_SUITE_data/ei_runner.c
@@ -18,11 +18,13 @@
* %CopyrightEnd%
*/
#include <stdio.h>
+#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
+#include <ctype.h>
#ifndef __WIN32__
#include <unistd.h>
#endif
diff --git a/lib/erl_interface/test/ei_connect_SUITE_data/einode.c b/lib/erl_interface/test/ei_connect_SUITE_data/einode.c
index 083ca1d372..8af8760f30 100644
--- a/lib/erl_interface/test/ei_connect_SUITE_data/einode.c
+++ b/lib/erl_interface/test/ei_connect_SUITE_data/einode.c
@@ -21,6 +21,7 @@
/* to test multiple threads in ei */
#include <stdlib.h>
+#include <string.h>
#include <stdio.h>
#ifdef __WIN32__
@@ -30,12 +31,11 @@
#else
#include <pthread.h>
#include <sys/socket.h>
+#include <unistd.h>
#endif
#include "ei.h"
-#define MAIN main
-
/*
A small einode.
To be called from the test case ei_accept_SUITE:multi_thread
@@ -95,7 +95,7 @@ static void*
return 0;
}
-MAIN(int argc, char *argv[])
+int main(int argc, char *argv[])
{
int i, n, no_threads;
#ifdef __WIN32__
diff --git a/lib/erl_interface/test/ei_format_SUITE_data/ei_format_test.c b/lib/erl_interface/test/ei_format_SUITE_data/ei_format_test.c
index c6b4202b29..ef6011b491 100644
--- a/lib/erl_interface/test/ei_format_SUITE_data/ei_format_test.c
+++ b/lib/erl_interface/test/ei_format_SUITE_data/ei_format_test.c
@@ -18,6 +18,7 @@
* %CopyrightEnd%
*/
+#include <stdlib.h>
#include "ei_runner.h"
#include <string.h>
diff --git a/lib/erl_interface/test/ei_print_SUITE.erl b/lib/erl_interface/test/ei_print_SUITE.erl
index 25dd95649d..43d74066a2 100644
--- a/lib/erl_interface/test/ei_print_SUITE.erl
+++ b/lib/erl_interface/test/ei_print_SUITE.erl
@@ -27,7 +27,8 @@
-export([all/0, suite/0,
init_per_testcase/2,
atoms/1, tuples/1, lists/1, strings/1,
- maps/1, funs/1, binaries/1, bitstrings/1]).
+ maps/1, funs/1, binaries/1, bitstrings/1,
+ integers/1]).
-import(runner, [get_term/1]).
@@ -38,7 +39,7 @@ suite() ->
[{ct_hooks,[ts_install_cth]}].
all() ->
- [atoms, tuples, lists, strings, maps, funs, binaries, bitstrings].
+ [atoms, tuples, lists, strings, maps, funs, binaries, bitstrings, integers].
init_per_testcase(Case, Config) ->
runner:init_per_testcase(?MODULE, Case, Config).
@@ -198,6 +199,69 @@ bitstrings(Config) ->
runner:recv_eot(P),
ok.
+integers(Config) ->
+ Port = runner:start(Config, ?integers),
+
+ test_integers(Port, -1000, 1000),
+ test_integers(Port, (1 bsl 27) - 1000, (1 bsl 27) + 1000),
+ test_integers(Port, -(1 bsl 27) - 1000, -(1 bsl 27) + 1000),
+ test_integers(Port, (1 bsl 28) - 1000, (1 bsl 28) + 1000),
+ test_integers(Port, -(1 bsl 28) - 1000, -(1 bsl 28) + 1000),
+ test_integers(Port, (1 bsl 31) - 1000, (1 bsl 31) + 1000),
+ test_integers(Port, -(1 bsl 31) - 1000, -(1 bsl 31) + 1000),
+ test_integers(Port, (1 bsl 32) - 1000, (1 bsl 32) + 1000),
+ test_integers(Port, -(1 bsl 32) - 1000, -(1 bsl 32) + 1000),
+ test_integers(Port, (1 bsl 60) - 1000, (1 bsl 60) + 1000),
+ test_integers(Port, -(1 bsl 60) - 1000, -(1 bsl 60) + 1000),
+ test_integers(Port, 16#feeddeaddeadbeef - 1000, 16#feeddeaddeadbeef + 1000),
+ test_integers(Port, -16#feeddeaddeadbeef - 1000, -16#feeddeaddeadbeef + 1000),
+ test_integers(Port, (1 bsl 64) - 1000, (1 bsl 64) + 1000),
+ test_integers(Port, 16#addfeeddeaddeadbeef - 1000, 16#addfeeddeaddeadbeef + 1000),
+ test_integers(Port, -16#addfeeddeaddeadbeef - 1000, -16#addfeeddeaddeadbeef + 1000),
+ test_integers(Port, -(1 bsl 64) - 1000, -(1 bsl 64) + 1000),
+ test_integers(Port, (1 bsl 8192) - 1000, (1 bsl 8192) + 1000),
+ test_integers(Port, -(1 bsl 8192) - 1000, -(1 bsl 8192) + 1000),
+
+ "done" = send_term_get_printed(Port, done),
+
+ runner:recv_eot(Port),
+
+ ok.
+
+test_integer(Port, Int, Print) when is_integer(Int) ->
+ Res = send_term_get_printed(Port, Int),
+ case Print of
+ true ->
+ io:format("Res: ~s~n", [Res]);
+ false ->
+ ok
+ end,
+ %% Large bignums are printed in base 16...
+ Exp = case Res of
+ "16#" ++ _ ->
+ "16#" ++ integer_to_list(Int, 16);
+ "-16#" ++ _ ->
+ "-16#" ++ integer_to_list(-1*Int, 16);
+ _ ->
+ integer_to_list(Int)
+ end,
+ case Exp =:= Res of
+ true ->
+ ok;
+ false ->
+ io:format("Exp: ~s~nRes: ~s~n", [Exp, Res]),
+ ct:fail({Exp, Res})
+ end.
+
+test_integers(Port, FromInt, ToInt) ->
+ test_integers(Port, FromInt, ToInt, true).
+
+test_integers(_Port, FromInt, ToInt, _Print) when FromInt > ToInt ->
+ ok;
+test_integers(Port, FromInt, ToInt, Print) ->
+ ok = test_integer(Port, FromInt, Print),
+ NewFromInt = FromInt + 1,
+ test_integers(Port, NewFromInt, ToInt, NewFromInt == ToInt).
send_term_get_printed(Port, Term) ->
diff --git a/lib/erl_interface/test/ei_print_SUITE_data/ei_print_test.c b/lib/erl_interface/test/ei_print_SUITE_data/ei_print_test.c
index 4b23701e82..b840c4aca0 100644
--- a/lib/erl_interface/test/ei_print_SUITE_data/ei_print_test.c
+++ b/lib/erl_interface/test/ei_print_SUITE_data/ei_print_test.c
@@ -345,3 +345,70 @@ TESTCASE(bitstrings)
}
report(1);
}
+
+TESTCASE(integers)
+{
+ char *buf;
+ long len;
+ int err, n, index, done;
+ ei_x_buff x;
+
+ ei_init();
+
+ done = 0;
+ do {
+ int type, type_len;
+ buf = read_packet(NULL);
+
+ index = 0;
+ err = ei_decode_version(buf, &index, NULL);
+ if (err != 0)
+ fail1("ei_decode_version returned %d", err);
+ err = ei_get_type(buf, &index, &type, &type_len);
+ if (err)
+ fail1("ei_get_type() returned %d", err);
+ switch (type) {
+ case ERL_SMALL_INTEGER_EXT:
+ case ERL_INTEGER_EXT: {
+ long val;
+ err = ei_decode_long(buf, &index, &val);
+ if (err)
+ fail1("ei_decode_long() returned %d", err);
+ break;
+ }
+ case ERL_SMALL_BIG_EXT:
+ case ERL_LARGE_BIG_EXT: {
+ erlang_big *big = ei_alloc_big(type_len);
+ if (!big)
+ fail1("ei_alloc_big() failed %d", ENOMEM);
+ err = ei_decode_big(buf, &index, big);
+ if (err)
+ fail1("ei_decode_big() failed %d", err);
+ ei_free_big(big);
+ break;
+ }
+ case ERL_ATOM_EXT: {
+ char abuf[MAXATOMLEN];
+ err = ei_decode_atom(buf, &index, &abuf[0]);
+ if (err)
+ fail1("ei_decode_atom() failed %d", err);
+ if (strcmp("done", &abuf[0]) == 0)
+ done = 1;
+ break;
+ }
+ default:
+ fail1("Unexpected type %d", type);
+ break;
+ }
+
+ ei_x_new(&x);
+ ei_x_append_buf(&x, buf, index);
+ send_printed_buf(&x);
+ ei_x_free(&x);
+
+ free_packet(buf);
+
+ } while (!done);
+
+ report(1);
+}
diff --git a/lib/erl_interface/test/ei_tmo_SUITE_data/ei_tmo_test.c b/lib/erl_interface/test/ei_tmo_SUITE_data/ei_tmo_test.c
index 5f126f370a..0fd3e1d697 100644
--- a/lib/erl_interface/test/ei_tmo_SUITE_data/ei_tmo_test.c
+++ b/lib/erl_interface/test/ei_tmo_SUITE_data/ei_tmo_test.c
@@ -29,6 +29,7 @@
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
+#include <unistd.h>
#endif
#include "ei_runner.h"
diff --git a/lib/erl_interface/vsn.mk b/lib/erl_interface/vsn.mk
index e454f9e1d1..d928057ca4 100644
--- a/lib/erl_interface/vsn.mk
+++ b/lib/erl_interface/vsn.mk
@@ -1,2 +1,2 @@
-EI_VSN = 4.0.1
+EI_VSN = 4.0.2
ERL_INTERFACE_VSN = $(EI_VSN)
diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml
index 22f2cf6f3f..e1bcd5de97 100644
--- a/lib/inets/doc/src/notes.xml
+++ b/lib/inets/doc/src/notes.xml
@@ -33,7 +33,22 @@
<file>notes.xml</file>
</header>
- <section><title>Inets 7.3</title>
+ <section><title>Inets 7.3.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix an issue about HTML-escaped filename in inets.</p>
+ <p>
+ Own Id: OTP-16873 Aux Id: ERL-330 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Inets 7.3</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/inets/src/http_server/mod_alias.erl b/lib/inets/src/http_server/mod_alias.erl
index 0d1681f6ed..35da39c53c 100644
--- a/lib/inets/src/http_server/mod_alias.erl
+++ b/lib/inets/src/http_server/mod_alias.erl
@@ -196,6 +196,7 @@ append_index(RealName, [Index | Rest]) ->
%% path
path(Data, ConfigDB, RequestURI) ->
+ InitPath =
case proplists:get_value(real_name, Data) of
undefined ->
{Prefix, DocumentRoot} = which_document_root(ConfigDB),
@@ -204,6 +205,10 @@ path(Data, ConfigDB, RequestURI) ->
Prefix ++ Path;
{Path, _AfterPath} ->
Path
+ end,
+ case uri_string:percent_decode(InitPath) of
+ {error, _} -> InitPath;
+ P -> P
end.
%%
diff --git a/lib/inets/src/http_server/mod_dir.erl b/lib/inets/src/http_server/mod_dir.erl
index ad2ee1d994..3d287c4b18 100644
--- a/lib/inets/src/http_server/mod_dir.erl
+++ b/lib/inets/src/http_server/mod_dir.erl
@@ -100,6 +100,17 @@ dir(Path,RequestURI,ConfigDB) ->
file:format_error(Reason))}
end.
+encode_html_entity(FileName) ->
+ Enc = fun($&) -> "&amp;";
+ ($<) -> "&lt;";
+ ($>) -> "&gt;";
+ ($") -> "&quot;";
+ ($') -> "&#x27;";
+ ($/) -> "&#x2F;";
+ (C) -> C
+ end,
+ unicode:characters_to_list([Enc(C) || C <- FileName]).
+
%% header
header(Path,RequestURI) ->
@@ -124,7 +135,7 @@ format(Path,RequestURI) ->
io_lib:format("<IMG SRC=\"~s\" ALT=\"[~s]\">"
" <A HREF=\"~s\">Parent directory</A> "
" ~2.2.0w-~s-~w ~2.2.0w:~2.2.0w -\n",
- [icon(back),"DIR",RequestURI,Day,
+ [icon(back),"DIR",get_href(RequestURI),Day,
httpd_util:month(Month),Year,Hour,Minute]).
%% body
@@ -135,8 +146,9 @@ body(Path, RequestURI, ConfigDB, [Entry | Rest]) ->
[format(Path, RequestURI, ConfigDB, Entry)|
body(Path, RequestURI, ConfigDB, Rest)].
-format(Path,RequestURI,ConfigDB,Entry) ->
- case file:read_file_info(Path++"/"++Entry) of
+format(Path,RequestURI,ConfigDB,InitEntry) ->
+ Entry = encode_html_entity(InitEntry),
+ case file:read_file_info(Path++"/"++InitEntry) of
{ok,FileInfo} when FileInfo#file_info.type == directory ->
{{Year, Month, Day},{Hour, Minute, _Second}} =
FileInfo#file_info.mtime,
@@ -145,18 +157,18 @@ format(Path,RequestURI,ConfigDB,Entry) ->
EntryLength > 21 ->
io_lib:format("<IMG SRC=\"~s\" ALT=\"[~s]\"> "
"<A HREF=\"~s\">~-21.s..</A>"
- "~2.2.0w-~s-~w ~2.2.0w:~2.2.0w"
+ "~*.*c~2.2.0w-~s-~w ~2.2.0w:~2.2.0w"
" -\n", [icon(folder),"DIR",
- RequestURI++"/"++Entry++"/",
- Entry,
+ get_href(RequestURI++"/"++InitEntry++"/"),
+ Entry, 23-21, 23-21, $ ,
Day, httpd_util:month(Month),
Year,Hour,Minute]);
true ->
io_lib:format("<IMG SRC=\"~s\" ALT=\"[~s]\">"
" <A HREF=\"~s\">~s</A>~*.*c~2.2.0"
"w-~s-~w ~2.2.0w:~2.2.0w -\n",
- [icon(folder),"DIR",RequestURI ++ "/" ++
- Entry ++ "/",Entry,
+ [icon(folder),"DIR",get_href(RequestURI ++ "/" ++
+ InitEntry ++ "/"),Entry,
23-EntryLength,23-EntryLength,$ ,Day,
httpd_util:month(Month),Year,Hour,Minute])
end;
@@ -169,10 +181,10 @@ format(Path,RequestURI,ConfigDB,Entry) ->
if
EntryLength > 21 ->
io_lib:format("<IMG SRC=\"~s\" ALT=\"[~s]\">"
- " <A HREF=\"~s\">~-21.s..</A>~2.2.0"
+ " <A HREF=\"~s\">~-21.s..</A>~*.*c~2.2.0"
"w-~s-~w ~2.2.0w:~2.2.0w~8wk ~s\n",
- [icon(Suffix, MimeType), Suffix, RequestURI
- ++"/"++Entry, Entry,Day,
+ [icon(Suffix, MimeType), Suffix, get_href(RequestURI
+ ++"/"++InitEntry), Entry, 23-21, 23-21, $ , Day,
httpd_util:month(Month),Year,Hour,Minute,
trunc(FileInfo#file_info.size/1024+1),
MimeType]);
@@ -180,8 +192,8 @@ format(Path,RequestURI,ConfigDB,Entry) ->
io_lib:format("<IMG SRC=\"~s\" ALT=\"[~s]\"> "
"<A HREF=\"~s\">~s</A>~*.*c~2.2.0w-~s-~w"
" ~2.2.0w:~2.2.0w~8wk ~s\n",
- [icon(Suffix, MimeType), Suffix, RequestURI
- ++ "/" ++ Entry, Entry, 23-EntryLength,
+ [icon(Suffix, MimeType), Suffix, get_href(RequestURI
+ ++ "/" ++ InitEntry), Entry, 23-EntryLength,
23-EntryLength, $ ,Day,
httpd_util:month(Month),Year,Hour,Minute,
trunc(FileInfo#file_info.size/1024+1),
@@ -191,6 +203,37 @@ format(Path,RequestURI,ConfigDB,Entry) ->
""
end.
+get_href(URI) ->
+ percent_encode(URI).
+
+percent_encode(URI) when is_list(URI) ->
+ Reserved = reserved(),
+ lists:append([uri_encode(Char, Reserved) || Char <- URI]);
+percent_encode(URI) when is_binary(URI) ->
+ Reserved = reserved(),
+ << <<(uri_encode_binary(Char, Reserved))/binary>> || <<Char>> <= URI >>.
+
+reserved() ->
+ sets:from_list([$;, $:, $@, $&, $=, $+, $,, $/, $?,
+ $#, $[, $], $<, $>, $\", ${, $}, $|, %"
+ $\\, $', $^, $%, $ ]).
+
+uri_encode(Char, Reserved) ->
+ case sets:is_element(Char, Reserved) of
+ true ->
+ [ $% | http_util:integer_to_hexlist(Char)];
+ false ->
+ [Char]
+ end.
+
+uri_encode_binary(Char, Reserved) ->
+ case sets:is_element(Char, Reserved) of
+ true ->
+ << $%, (integer_to_binary(Char, 16))/binary >>;
+ false ->
+ <<Char>>
+ end.
+
%% footer
footer(Path,FileList) ->
diff --git a/lib/inets/test/httpd_SUITE_data/cgi_echo.c b/lib/inets/test/httpd_SUITE_data/cgi_echo.c
index 580f860e96..e90b125a00 100644
--- a/lib/inets/test/httpd_SUITE_data/cgi_echo.c
+++ b/lib/inets/test/httpd_SUITE_data/cgi_echo.c
@@ -4,6 +4,8 @@
#if defined __WIN32__
#include <windows.h>
#include <fcntl.h>
+#else
+#include <unistd.h>
#endif
static int read_exact(char *buffer, int len);
diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk
index f323be91d0..195b20f63c 100644
--- a/lib/inets/vsn.mk
+++ b/lib/inets/vsn.mk
@@ -19,6 +19,6 @@
# %CopyrightEnd%
APPLICATION = inets
-INETS_VSN = 7.3
+INETS_VSN = 7.3.1
PRE_VSN =
APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)"
diff --git a/lib/kernel/doc/src/code.xml b/lib/kernel/doc/src/code.xml
index 81186b6876..967b868b5e 100644
--- a/lib/kernel/doc/src/code.xml
+++ b/lib/kernel/doc/src/code.xml
@@ -87,7 +87,7 @@
directory described above, except that directories without
an <c>ebin</c> directory are ignored.</p>
<p>All application directories found in the additional directories
- appears before the standard OTP applications, except for the
+ appear before the standard OTP applications, except for the
Kernel and STDLIB applications, which are placed before
any additional applications. In other words, modules found in any
of the additional library directories override modules with
@@ -779,8 +779,8 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
<c>{error,missing}</c>.
</p>
<p>For more information about the documentation chunk see
- <seeguide marker="erl_docgen:doc_storage">Documentation Storage</seeguide>
- in Erl_Docgen's User's Guide.</p>
+ <seeguide marker="kernel:eep48_chapter">Documentation Storage and Format</seeguide>
+ in Kernel's User's Guide.</p>
</desc>
</func>
<func>
diff --git a/lib/kernel/doc/src/eep48_chapter.xml b/lib/kernel/doc/src/eep48_chapter.xml
index db5d13eb7a..2173dfd949 100644
--- a/lib/kernel/doc/src/eep48_chapter.xml
+++ b/lib/kernel/doc/src/eep48_chapter.xml
@@ -38,6 +38,9 @@
<p>To fetch the EEP-48 documentation for a module you can use
<seemfa marker="code#get_doc/1"><c>code:get_doc/1</c></seemfa>.</p>
+ <p>To render the EEP-48 documentation for an Erlang module you can use
+ <seemfa marker="stdlib:shell_docs#render/2"><c>shell_docs:render/2</c></seemfa>.</p>
+
<section>
<title>the "Docs" storage</title>
<p>To look for documentation for a module name example, a tool should:</p>
diff --git a/lib/kernel/doc/src/inet_res.xml b/lib/kernel/doc/src/inet_res.xml
index c30038f4cd..f50915a2e2 100644
--- a/lib/kernel/doc/src/inet_res.xml
+++ b/lib/kernel/doc/src/inet_res.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2009</year><year>2018</year>
+ <year>2009</year><year>2020</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -48,31 +48,41 @@
<section>
<title>Name Resolving</title>
<p>UDP queries are used unless resolver option
- <c>usevc</c> is <c>true</c>, which forces TCP queries.
- If the query is too large for UDP, TCP is used instead.
- For regular DNS queries, 512 bytes is the size limit.</p>
+ <c>usevc</c> is <c>true</c>, which forces TCP queries.
+ If the query is too large for UDP, TCP is used instead.
+ For regular DNS queries, 512 bytes is the size limit.</p>
+
<p>When EDNS is enabled (resolver option
- <c>edns</c> is set to the EDNS version (that is, <c>0</c>
- instead of <c>false</c>), resolver option
- <c>udp_payload_size</c> sets the limit. If a name server
- replies with the TC bit set (truncation), indicating that
- the answer is incomplete, the query is retried
- to that name server using TCP. Resolver option
- <c>udp_payload_size</c> also sets the advertised
- size for the maximum allowed reply size, if EDNS is
- enabled, otherwise the name server uses the limit
- 512 bytes. If the reply is larger, it gets truncated,
- forcing a TCP requery.</p>
+ <c>edns</c> is set to the EDNS version (that is, <c>0</c>
+ instead of <c>false</c>), resolver option
+ <c>udp_payload_size</c> sets the limit. If a name server
+ replies with the TC bit set (truncation), indicating that
+ the answer is incomplete, the query is retried
+ to that name server using TCP. Resolver option
+ <c>udp_payload_size</c> also sets the advertised
+ size for the maximum allowed reply size, if EDNS is
+ enabled, otherwise the name server uses the limit
+ 512 bytes. If the reply is larger, it gets truncated,
+ forcing a TCP requery.</p>
+
<p>For UDP queries, resolver options <c>timeout</c>
- and <c>retry</c> control retransmission.
- Each name server in the <c>nameservers</c> list is
- tried with a time-out of <c>timeout</c>/<c>retry</c>.
- Then all name servers are tried again, doubling the
- time-out, for a total of <c>retry</c> times.</p>
+ and <c>retry</c> control retransmission.
+ Each name server in the <c>nameservers</c> list is
+ tried with a time-out of <c>timeout</c>/<c>retry</c>.
+ Then all name servers are tried again, doubling the
+ time-out, for a total of <c>retry</c> times.</p>
+
+ <marker id="servfail_retry_timeout"/>
+ <p>But before all name servers are tried again, there is a
+ (user configurable) timeout, <c>servfail_retry_timeout</c>.
+ The point of this is to prevent the new query to be handled
+ by to the servfail cache (a client that is to eager will
+ actually only get what is in the servfail cache). </p>
+
<p>For queries not using the <c>search</c> list,
- if the query to all <c>nameservers</c> results in
- <c>{error,nxdomain}</c> or an empty answer, the same
- query is tried for <c>alt_nameservers</c>.</p>
+ if the query to all <c>nameservers</c> results in
+ <c>{error,nxdomain}</c> or an empty answer, the same
+ query is tried for <c>alt_nameservers</c>.</p>
</section>
<section>
diff --git a/lib/kernel/doc/src/notes.xml b/lib/kernel/doc/src/notes.xml
index d69fbdcd13..27713c0acc 100644
--- a/lib/kernel/doc/src/notes.xml
+++ b/lib/kernel/doc/src/notes.xml
@@ -31,6 +31,77 @@
</header>
<p>This document describes the changes made to the Kernel application.</p>
+<section><title>Kernel 7.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The <c>apply</c> call's in <c>logger.hrl</c> are now
+ called with <c>erlang</c> prefix to avoid clashed with
+ local <c>apply/3</c> functions.</p>
+ <p>
+ Own Id: OTP-16976 Aux Id: PR-2807 </p>
+ </item>
+ <item>
+ <p>
+ Fix memory leak in <c>pg</c>.</p>
+ <p>
+ Own Id: OTP-17034 Aux Id: PR-2866 </p>
+ </item>
+ <item>
+ <p>
+ Fix crash in <c>logger_proxy</c> due to stray
+ <c>gen_server:call</c> replies not being handled. The
+ stray replies come when logger is under heavy load and
+ the flow control mechanism is reaching its limit.</p>
+ <p>
+ Own Id: OTP-17038</p>
+ </item>
+ <item>
+ <p>
+ Fixed a bug in <c>erl_epmd:names()</c> that caused it to
+ return the illegal return value <c>noport</c> instead of
+ <c>{error, Reason}</c> where <c>Reason</c> is the actual
+ error reason. This bug also propagated to
+ <c>net_adm:names()</c>.</p>
+ <p>
+ This bug was introduced in <c>kernel</c> version 7.1 (OTP
+ 23.1).</p>
+ <p>
+ Own Id: OTP-17054 Aux Id: ERL-1424 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Add export of some resolver documented types.</p>
+ <p>
+ Own Id: OTP-16954 Aux Id: ERIERL-544 </p>
+ </item>
+ <item>
+ <p>
+ Add configurable retry timeout for resolver lookups.</p>
+ <p>
+ Own Id: OTP-16956 Aux Id: ERIERL-547 </p>
+ </item>
+ <item>
+ <p>
+ <c>gen_server:multi_call()</c> has been optimized in the
+ special case of only calling the local node with timeout
+ set to <c>infinity</c>.</p>
+ <p>
+ Own Id: OTP-17058 Aux Id: PR-2887 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Kernel 7.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/kernel/include/logger.hrl b/lib/kernel/include/logger.hrl
index b09977e0f2..bd17f7efc4 100644
--- a/lib/kernel/include/logger.hrl
+++ b/lib/kernel/include/logger.hrl
@@ -46,7 +46,7 @@
-define(DO_LOG(Level,Args),
case logger:allow(Level,?MODULE) of
true ->
- apply(logger,macro_log,[?LOCATION,Level|Args]);
+ erlang:apply(logger,macro_log,[?LOCATION,Level|Args]);
false ->
ok
end).
diff --git a/lib/kernel/src/erl_epmd.erl b/lib/kernel/src/erl_epmd.erl
index 7ba5217d51..7cc84b2475 100644
--- a/lib/kernel/src/erl_epmd.erl
+++ b/lib/kernel/src/erl_epmd.erl
@@ -101,9 +101,9 @@ port_please(Node, HostName, Timeout) ->
case getepmdbyname(HostName, Timeout) of
{ok, EpmdAddr} ->
get_port(Node, EpmdAddr, Timeout);
- Error ->
- ?port_please_failure2(Error),
- Error
+ _Error ->
+ ?port_please_failure2(_Error),
+ noport
end;
{ok, Prt} ->
%% We don't know which dist version the other node is running
@@ -123,8 +123,8 @@ getepmdbyname(HostName, Timeout) when is_list(HostName) ->
case inet:gethostbyname(HostName, Family, Timeout) of
{ok,#hostent{ h_addr_list = [EpmdAddr | _]}} ->
{ok, EpmdAddr};
- _Else ->
- noport
+ Else ->
+ Else
end;
getepmdbyname(HostName, _Timeout) ->
{ok, HostName}.
diff --git a/lib/kernel/src/inet_db.erl b/lib/kernel/src/inet_db.erl
index 814c3f4276..ff5bdd8f26 100644
--- a/lib/kernel/src/inet_db.erl
+++ b/lib/kernel/src/inet_db.erl
@@ -44,7 +44,8 @@
del_socks_methods/1, del_socks_methods/0,
add_socks_noproxy/1, del_socks_noproxy/1]).
-export([set_cache_size/1, set_cache_refresh/1]).
--export([set_timeout/1, set_retry/1, set_inet6/1, set_usevc/1]).
+-export([set_timeout/1, set_retry/1, set_servfail_retry_timeout/1,
+ set_inet6/1, set_usevc/1]).
-export([set_edns/1, set_udp_payload_size/1]).
-export([set_resolv_conf/1, set_hosts_file/1, get_hosts_file/0]).
-export([tcp_module/0, set_tcp_module/1]).
@@ -221,6 +222,9 @@ set_timeout(Time) -> res_option(timeout, Time).
set_retry(N) -> res_option(retry, N).
+set_servfail_retry_timeout(Time) when is_integer(Time) andalso (Time >= 0) ->
+ res_option(servfail_retry_timeout, Time).
+
set_inet6(Bool) -> res_option(inet6, Bool).
set_usevc(Bool) -> res_option(usevc, Bool).
@@ -313,42 +317,104 @@ valid_lookup() -> [dns, file, yp, nis, nisplus, native].
%% Reconstruct an inetrc sturcture from inet_db
get_rc() ->
get_rc([hosts, domain, nameservers, search, alt_nameservers,
- timeout, retry, inet6, usevc,
+ timeout, retry, servfail_retry_timeout, inet6, usevc,
edns, udp_payload_size, resolv_conf, hosts_file,
socks5_server, socks5_port, socks5_methods, socks5_noproxy,
udp, sctp, tcp, host, cache_size, cache_refresh, lookup], []).
get_rc([K | Ks], Ls) ->
case K of
- hosts -> get_rc_hosts(Ks, Ls, inet_hosts_byaddr);
- domain -> get_rc(domain, res_domain, "", Ks, Ls);
- nameservers -> get_rc_ns(db_get(res_ns),nameservers,Ks,Ls);
- alt_nameservers -> get_rc_ns(db_get(res_alt_ns),alt_nameservers,Ks,Ls);
- search -> get_rc(search, res_search, [], Ks, Ls);
- timeout -> get_rc(timeout,res_timeout,?RES_TIMEOUT, Ks,Ls);
- retry -> get_rc(retry, res_retry, ?RES_RETRY, Ks, Ls);
- inet6 -> get_rc(inet6, res_inet6, false, Ks, Ls);
- usevc -> get_rc(usevc, res_usevc, false, Ks, Ls);
- edns -> get_rc(edns, res_edns, false, Ks, Ls);
- udp_payload_size -> get_rc(udp_payload_size, res_udp_payload_size,
- ?DNS_UDP_PAYLOAD_SIZE, Ks, Ls);
- resolv_conf -> get_rc(resolv_conf, res_resolv_conf, undefined, Ks, Ls);
- hosts_file -> get_rc(hosts_file, res_hosts_file, undefined, Ks, Ls);
- tcp -> get_rc(tcp, tcp_module, ?DEFAULT_TCP_MODULE, Ks, Ls);
- udp -> get_rc(udp, udp_module, ?DEFAULT_UDP_MODULE, Ks, Ls);
- sctp -> get_rc(sctp, sctp_module, ?DEFAULT_SCTP_MODULE, Ks, Ls);
- lookup -> get_rc(lookup, res_lookup, [native,file], Ks, Ls);
- cache_size -> get_rc(cache_size, cache_size, ?CACHE_LIMIT, Ks, Ls);
- cache_refresh ->
- get_rc(cache_refresh, cache_refresh_interval,?CACHE_REFRESH,Ks,Ls);
- socks5_server -> get_rc(socks5_server, socks5_server, "", Ks, Ls);
- socks5_port -> get_rc(socks5_port,socks5_port,?IPPORT_SOCKS,Ks,Ls);
- socks5_methods -> get_rc(socks5_methods,socks5_methods,[none],Ks,Ls);
- socks5_noproxy ->
- case db_get(socks5_noproxy) of
- [] -> get_rc(Ks, Ls);
- NoProxy -> get_rc_noproxy(NoProxy, Ks, Ls)
- end;
+ hosts -> get_rc_hosts(Ks, Ls, inet_hosts_byaddr);
+ domain -> get_rc(domain,
+ res_domain,
+ "",
+ Ks, Ls);
+ nameservers -> get_rc_ns(db_get(res_ns),
+ nameservers,
+ Ks, Ls);
+ alt_nameservers -> get_rc_ns(db_get(res_alt_ns),
+ alt_nameservers,
+ Ks, Ls);
+ search -> get_rc(search,
+ res_search,
+ [],
+ Ks, Ls);
+ timeout -> get_rc(timeout,
+ res_timeout,
+ ?RES_TIMEOUT,
+ Ks, Ls);
+ retry -> get_rc(retry,
+ res_retry,
+ ?RES_RETRY,
+ Ks, Ls);
+ servfail_retry_timeout -> get_rc(servfail_retry_timeout,
+ res_servfail_retry_timeout,
+ ?RES_SERVFAIL_RETRY_TO,
+ Ks, Ls);
+ inet6 -> get_rc(inet6,
+ res_inet6,
+ false,
+ Ks, Ls);
+ usevc -> get_rc(usevc,
+ res_usevc,
+ false,
+ Ks, Ls);
+ edns -> get_rc(edns,
+ res_edns,
+ false,
+ Ks, Ls);
+ udp_payload_size -> get_rc(udp_payload_size,
+ res_udp_payload_size,
+ ?DNS_UDP_PAYLOAD_SIZE,
+ Ks, Ls);
+ resolv_conf -> get_rc(resolv_conf,
+ res_resolv_conf,
+ undefined,
+ Ks, Ls);
+ hosts_file -> get_rc(hosts_file,
+ res_hosts_file,
+ undefined,
+ Ks, Ls);
+ tcp -> get_rc(tcp,
+ tcp_module,
+ ?DEFAULT_TCP_MODULE,
+ Ks, Ls);
+ udp -> get_rc(udp,
+ udp_module,
+ ?DEFAULT_UDP_MODULE,
+ Ks, Ls);
+ sctp -> get_rc(sctp,
+ sctp_module,
+ ?DEFAULT_SCTP_MODULE,
+ Ks, Ls);
+ lookup -> get_rc(lookup,
+ res_lookup,
+ [native, file],
+ Ks, Ls);
+ cache_size -> get_rc(cache_size,
+ cache_size,
+ ?CACHE_LIMIT,
+ Ks, Ls);
+ cache_refresh -> get_rc(cache_refresh,
+ cache_refresh_interval,
+ ?CACHE_REFRESH,
+ Ks, Ls);
+ socks5_server -> get_rc(socks5_server,
+ socks5_server,
+ "",
+ Ks, Ls);
+ socks5_port -> get_rc(socks5_port,
+ socks5_port,
+ ?IPPORT_SOCKS,
+ Ks, Ls);
+ socks5_methods -> get_rc(socks5_methods,
+ socks5_methods,
+ [none],
+ Ks, Ls);
+ socks5_noproxy -> case db_get(socks5_noproxy) of
+ [] -> get_rc(Ks, Ls);
+ NoProxy -> get_rc_noproxy(NoProxy, Ks, Ls)
+ end;
_ ->
get_rc(Ks, Ls)
end;
@@ -415,6 +481,7 @@ res_optname(lookup) -> res_lookup;
res_optname(recurse) -> res_recurse;
res_optname(search) -> res_search;
res_optname(retry) -> res_retry;
+res_optname(servfail_retry_timeout) -> res_servfail_retry_timeout;
res_optname(timeout) -> res_timeout;
res_optname(inet6) -> res_inet6;
res_optname(usevc) -> res_usevc;
@@ -448,6 +515,7 @@ res_check_option(recurse, R) when is_boolean(R) -> true;
res_check_option(search, SearchList) ->
res_check_list(SearchList, fun res_check_search/1);
res_check_option(retry, N) when is_integer(N), N > 0 -> true;
+res_check_option(servfail_retry_timeout, T) when is_integer(T), T >= 0 -> true;
res_check_option(timeout, T) when is_integer(T), T > 0 -> true;
res_check_option(inet6, Bool) when is_boolean(Bool) -> true;
res_check_option(usevc, Bool) when is_boolean(Bool) -> true;
@@ -787,6 +855,7 @@ lookup_socket(Socket) when is_port(Socket) ->
%% res_usevc Bool - use tcp only
%% res_id Integer - NS query identifier
%% res_retry Integer - Retry count for UDP query
+%% res_servfail_retry_timeout Integer - Timeout to next query after a failure
%% res_timeout Integer - UDP query timeout before retry
%% res_inet6 Bool - address family inet6 for gethostbyname/1
%% res_usevc Bool - use Virtual Circuit (TCP)
@@ -857,6 +926,7 @@ reset_db(Db) ->
{res_usevc, false},
{res_id, 0},
{res_retry, ?RES_RETRY},
+ {res_servfail_retry_timeout, ?RES_SERVFAIL_RETRY_TO},
{res_timeout, ?RES_TIMEOUT},
{res_inet6, false},
{res_edns, false},
@@ -1526,6 +1596,7 @@ rc_reqname(_) -> undefined.
is_res_set(domain) -> true;
is_res_set(lookup) -> true;
is_res_set(timeout) -> true;
+is_res_set(servfail_retry_timeout) -> true;
is_res_set(retry) -> true;
is_res_set(inet6) -> true;
is_res_set(usevc) -> true;
diff --git a/lib/kernel/src/inet_res.erl b/lib/kernel/src/inet_res.erl
index 7886ef83ac..e03cf76994 100644
--- a/lib/kernel/src/inet_res.erl
+++ b/lib/kernel/src/inet_res.erl
@@ -37,6 +37,10 @@
-export([nslookup/3, nslookup/4]).
-export([nnslookup/4, nnslookup/5]).
+-export_type([res_option/0,
+ res_error/0,
+ nameserver/0]).
+
-include_lib("kernel/include/inet.hrl").
-include("inet_res.hrl").
-include("inet_dns.hrl").
@@ -259,7 +263,7 @@ do_nslookup(Name, Class, Type, Opts, Timeout) ->
%%
-record(options, { % These must be sorted!
alt_nameservers,edns,inet6,nameservers,recurse,
- retry,timeout,udp_payload_size,usevc,
+ retry,servfail_retry_timeout,timeout,udp_payload_size,usevc,
verbose}). % this is a local option, not in inet_db
%%
%% Opts when is_list(Opts) -> #options{}
@@ -772,8 +776,18 @@ query_retries(_Q, _NSs, _Timer, Retry, Retry, S, Reason) ->
query_retries(_Q, [], _Timer, _Retry, _I, S, Reason) ->
query_retries_error(S, Reason);
query_retries(Q, NSs, Timer, Retry, I, S_0, Reason) ->
+ servfail_retry_wait(Q, I),
query_nss(Q, NSs, Timer, Retry, I, S_0, Reason, NSs).
+servfail_retry_wait(_Q, 0) ->
+ ok;
+servfail_retry_wait(#q{options = #options{servfail_retry_timeout = T}}, _)
+ when (T > 0) ->
+ receive after T -> ok end;
+servfail_retry_wait(_, _) ->
+ ok.
+
+
%% Loop for all name servers, for each:
%% If EDNS is enabled, try that first,
%% and for selected failures fall back to plain DNS.
diff --git a/lib/kernel/src/inet_res.hrl b/lib/kernel/src/inet_res.hrl
index 774b4074a5..c812550328 100644
--- a/lib/kernel/src/inet_res.hrl
+++ b/lib/kernel/src/inet_res.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2016. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,23 +21,43 @@
%% Dns & resolver defintions
%%
--define(RES_TIMEOUT, 2000). %% milli second between retries
--define(RES_RETRY, 3). %% number of retry
--define(RES_FILE_UPDATE_TM, 5). %% seconds between file_info
+%% milli second for requests
+-define(RES_TIMEOUT, 2000).
--define(CACHE_LIMIT, 100). %% number of cached dns_rr
--define(CACHE_REFRESH, 60*60*1000). %% refresh interval
+%% milli second to wait before next request after a failure
+-define(RES_SERVFAIL_RETRY_TO, 1500).
+
+%% number of retry
+-define(RES_RETRY, 3).
+
+%% seconds between file_info
+-define(RES_FILE_UPDATE_TM, 5).
+
+%% number of cached dns_rr
+-define(CACHE_LIMIT, 100).
+
+%% refresh interval
+-define(CACHE_REFRESH, 60*60*1000).
+
+%% maximum packet size
+-define(PACKETSZ, 512).
+
+%% maximum domain name
+-define(MAXDNAME, 256).
+
+%% maximum compressed domain name
+-define(MAXCDNAME, 255).
+
+%% maximum length of domain label
+-define(MAXLABEL, 63).
--define(PACKETSZ, 512). %% maximum packet size
--define(MAXDNAME, 256). %% maximum domain name
--define(MAXCDNAME, 255). %% maximum compressed domain name
--define(MAXLABEL, 63). %% maximum length of domain label
%% Number of bytes of fixed size data in query structure
--define(QFIXEDSZ, 4).
+-define(QFIXEDSZ, 4).
+
%% number of bytes of fixed size data in resource record
--define(RRFIXEDSZ, 10).
+-define(RRFIXEDSZ, 10).
%%
%% Internet nameserver port number
%%
--define(NAMESERVER_PORT, 53).
+-define(NAMESERVER_PORT, 53).
diff --git a/lib/kernel/src/kernel.appup.src b/lib/kernel/src/kernel.appup.src
index f101cb40b3..e529a59bd3 100644
--- a/lib/kernel/src/kernel.appup.src
+++ b/lib/kernel/src/kernel.appup.src
@@ -48,7 +48,9 @@
{<<"^6\\.5\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^6\\.5\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^7\\.0$">>,[restart_new_emulator]},
- {<<"^7\\.0\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}],
+ {<<"^7\\.0\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^7\\.1$">>,[restart_new_emulator]},
+ {<<"^7\\.1\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}],
[{<<"^6\\.0$">>,[restart_new_emulator]},
{<<"^6\\.0\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^6\\.0\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
@@ -69,4 +71,6 @@
{<<"^6\\.5\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^6\\.5\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^7\\.0$">>,[restart_new_emulator]},
- {<<"^7\\.0\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}]}.
+ {<<"^7\\.0\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^7\\.1$">>,[restart_new_emulator]},
+ {<<"^7\\.1\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}]}.
diff --git a/lib/kernel/src/logger_proxy.erl b/lib/kernel/src/logger_proxy.erl
index d7d095899d..fb5296c262 100644
--- a/lib/kernel/src/logger_proxy.erl
+++ b/lib/kernel/src/logger_proxy.erl
@@ -134,7 +134,11 @@ handle_load({log,Level,Report,Meta},State) ->
%% Log event sent to this process e.g. from the emulator - it is really load
handle_info(Log,State) when is_tuple(Log), element(1,Log)==log ->
- {load,State}.
+ {load,State};
+handle_info(_Log,State) ->
+ %% Handle stray reply messages from sync try_log, not needed after OTP-24
+ %% as then aliases will prevent late messages.
+ State.
terminate(overloaded, _State) ->
_ = erlang:system_flag(system_logger,undefined),
diff --git a/lib/kernel/src/os.erl b/lib/kernel/src/os.erl
index 29a26674ba..b1488f1390 100644
--- a/lib/kernel/src/os.erl
+++ b/lib/kernel/src/os.erl
@@ -293,7 +293,11 @@ mk_cmd({win32,Wtype}, Cmd) ->
{Command, [], [], <<>>};
mk_cmd(_,Cmd) ->
%% Have to send command in like this in order to make sh commands like
- %% cd and ulimit available
+ %% cd and ulimit available.
+ %%
+ %% We use an absolute path here because we do not want the path to be
+ %% searched in case a stale NFS handle is somewhere in the path before
+ %% the sh command.
{"/bin/sh -s unix:cmd", [out],
%% We insert a new line after the command, in case the command
%% contains a comment character.
diff --git a/lib/kernel/src/pg.erl b/lib/kernel/src/pg.erl
index 580759a9bf..37265a5db0 100644
--- a/lib/kernel/src/pg.erl
+++ b/lib/kernel/src/pg.erl
@@ -285,9 +285,9 @@ handle_info({leave, Peer, PidOrPids, Groups}, #state{scope = Scope, nodes = Node
fun (Group, Acc) ->
case maps:get(Group, Acc) of
PidOrPids ->
- Acc;
+ maps:remove(Group, Acc);
[PidOrPids] ->
- Acc;
+ maps:remove(Group, Acc);
Existing when is_pid(PidOrPids) ->
Acc#{Group => lists:delete(PidOrPids, Existing)};
Existing ->
diff --git a/lib/kernel/test/erl_distribution_SUITE.erl b/lib/kernel/test/erl_distribution_SUITE.erl
index c57b87ca98..67faa4911c 100644
--- a/lib/kernel/test/erl_distribution_SUITE.erl
+++ b/lib/kernel/test/erl_distribution_SUITE.erl
@@ -45,7 +45,8 @@
monitor_nodes_many/1,
monitor_nodes_down_up/1,
dist_ctrl_proc_smoke/1,
- dist_ctrl_proc_reject/1]).
+ dist_ctrl_proc_reject/1,
+ erl_1424/1]).
%% Performs the test at another node.
-export([get_socket_priorities/0,
@@ -83,7 +84,8 @@ all() ->
dyn_node_name,
hidden_node, setopts,
table_waste, net_setuptime, inet_dist_options_options,
- {group, monitor_nodes}].
+ {group, monitor_nodes},
+ erl_1424].
groups() ->
[{monitor_nodes, [],
@@ -1703,6 +1705,11 @@ smoke_communicate(Node, OLoopMod, OLoopFun) ->
exit(Pid, kill),
ok.
+
+erl_1424(Config) when is_list(Config) ->
+ {error, Reason} = erl_epmd:names("."),
+ {comment, lists:flatten(io_lib:format("Reason: ~p", [Reason]))}.
+
%% Misc. functions
run_dist_configs(Func, Config) ->
diff --git a/erts/emulator/test/esock_misc/esock_iow_client.erl b/lib/kernel/test/esock_misc/esock_iow_client.erl
index 3e48a8f300..3e48a8f300 100644
--- a/erts/emulator/test/esock_misc/esock_iow_client.erl
+++ b/lib/kernel/test/esock_misc/esock_iow_client.erl
diff --git a/erts/emulator/test/esock_misc/esock_iow_lib.erl b/lib/kernel/test/esock_misc/esock_iow_lib.erl
index 6fc1365dca..6fc1365dca 100644
--- a/erts/emulator/test/esock_misc/esock_iow_lib.erl
+++ b/lib/kernel/test/esock_misc/esock_iow_lib.erl
diff --git a/erts/emulator/test/esock_misc/esock_iow_server.erl b/lib/kernel/test/esock_misc/esock_iow_server.erl
index 4b364a6ca6..4b364a6ca6 100644
--- a/erts/emulator/test/esock_misc/esock_iow_server.erl
+++ b/lib/kernel/test/esock_misc/esock_iow_server.erl
diff --git a/erts/emulator/test/esock_misc/socket_client.erl b/lib/kernel/test/esock_misc/socket_client.erl
index 891f7e67ab..891f7e67ab 100644
--- a/erts/emulator/test/esock_misc/socket_client.erl
+++ b/lib/kernel/test/esock_misc/socket_client.erl
diff --git a/erts/emulator/test/esock_misc/socket_lib.erl b/lib/kernel/test/esock_misc/socket_lib.erl
index e401a3195c..e401a3195c 100644
--- a/erts/emulator/test/esock_misc/socket_lib.erl
+++ b/lib/kernel/test/esock_misc/socket_lib.erl
diff --git a/erts/emulator/test/esock_misc/socket_server.erl b/lib/kernel/test/esock_misc/socket_server.erl
index 161ea17027..161ea17027 100644
--- a/erts/emulator/test/esock_misc/socket_server.erl
+++ b/lib/kernel/test/esock_misc/socket_server.erl
diff --git a/erts/emulator/test/esock_ttest/.gitignore b/lib/kernel/test/esock_ttest/.gitignore
index e69de29bb2..e69de29bb2 100644
--- a/erts/emulator/test/esock_ttest/.gitignore
+++ b/lib/kernel/test/esock_ttest/.gitignore
diff --git a/erts/emulator/test/esock_ttest/esock-ttest b/lib/kernel/test/esock_ttest/esock-ttest
index 2ded557484..2ded557484 100755
--- a/erts/emulator/test/esock_ttest/esock-ttest
+++ b/lib/kernel/test/esock_ttest/esock-ttest
diff --git a/erts/emulator/test/esock_ttest/esock-ttest-client b/lib/kernel/test/esock_ttest/esock-ttest-client
index 5ae05d03b8..5ae05d03b8 100755
--- a/erts/emulator/test/esock_ttest/esock-ttest-client
+++ b/lib/kernel/test/esock_ttest/esock-ttest-client
diff --git a/erts/emulator/test/esock_ttest/esock-ttest-server-gen b/lib/kernel/test/esock_ttest/esock-ttest-server-gen
index c29184772e..c29184772e 100755
--- a/erts/emulator/test/esock_ttest/esock-ttest-server-gen
+++ b/lib/kernel/test/esock_ttest/esock-ttest-server-gen
diff --git a/erts/emulator/test/esock_ttest/esock-ttest-server-sock b/lib/kernel/test/esock_ttest/esock-ttest-server-sock
index c443d42e64..c443d42e64 100755
--- a/erts/emulator/test/esock_ttest/esock-ttest-server-sock
+++ b/lib/kernel/test/esock_ttest/esock-ttest-server-sock
diff --git a/lib/kernel/test/inet_SUITE.erl b/lib/kernel/test/inet_SUITE.erl
index 29316816a7..8f8a10103f 100644
--- a/lib/kernel/test/inet_SUITE.erl
+++ b/lib/kernel/test/inet_SUITE.erl
@@ -1130,7 +1130,7 @@ gethostnative_debug_level(Config) when is_list(Config) ->
gethostnative_control(Config, Opts).
gethostnative_adjusted_opts(Config, CtrlSeq) ->
- Factor = ?config(gen_inet_factor, Config),
+ Factor = ?config(kernel_factor, Config),
gethostnative_adjusted_opts2(Factor, CtrlSeq).
gethostnative_adjusted_opts2(1, CtrlSeq) ->
diff --git a/lib/kernel/test/inet_res_SUITE.erl b/lib/kernel/test/inet_res_SUITE.erl
index 8b3f1aa2a9..91ff883466 100644
--- a/lib/kernel/test/inet_res_SUITE.erl
+++ b/lib/kernel/test/inet_res_SUITE.erl
@@ -24,11 +24,17 @@
-include_lib("kernel/include/inet.hrl").
-include_lib("kernel/src/inet_dns.hrl").
+-include("kernel_test_lib.hrl").
+
+
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
- init_per_testcase/2, end_per_testcase/2]).
+ init_per_testcase/2, end_per_testcase/2
+ ]).
-export([basic/1, resolve/1, edns0/1, txt_record/1, files_monitor/1,
- last_ms_answer/1, intermediate_error/1]).
+ last_ms_answer/1, intermediate_error/1,
+ servfail_retry_timeout_default/1, servfail_retry_timeout_1000/1
+ ]).
-export([
gethostbyaddr/0, gethostbyaddr/1,
gethostbyaddr_v6/0, gethostbyaddr_v6/1,
@@ -64,7 +70,8 @@ suite() ->
all() ->
[basic, resolve, edns0, txt_record, files_monitor,
- last_ms_answer, intermediate_error,
+ last_ms_answer,
+ intermediate_error, servfail_retry_timeout_default, servfail_retry_timeout_1000,
gethostbyaddr, gethostbyaddr_v6, gethostbyname,
gethostbyname_v6, getaddr, getaddr_v6, ipv4_to_ipv6,
host_and_addr].
@@ -72,11 +79,37 @@ all() ->
groups() ->
[].
-init_per_suite(Config) ->
- Config.
+init_per_suite(Config0) ->
+
+ ?P("init_per_suite -> entry with"
+ "~n Config: ~p"
+ "~n Nodes: ~p", [Config0, erlang:nodes()]),
+
+ case ?LIB:init_per_suite(Config0) of
+ {skip, _} = SKIP ->
+ SKIP;
+
+ Config1 when is_list(Config1) ->
+
+ ?P("init_per_suite -> end when "
+ "~n Config: ~p", [Config1]),
+
+ Config1
+ end.
+
+end_per_suite(Config0) ->
+
+ ?P("end_per_suite -> entry with"
+ "~n Config: ~p"
+ "~n Nodes: ~p", [Config0, erlang:nodes()]),
+
+ Config1 = ?LIB:end_per_suite(Config0),
+
+ ?P("end_per_suite -> "
+ "~n Nodes: ~p", [erlang:nodes()]),
+
+ Config1. %% We don't actually need to update or return config
-end_per_suite(_Config) ->
- ok.
init_per_group(_GroupName, Config) ->
Config.
@@ -86,36 +119,56 @@ end_per_group(_GroupName, Config) ->
zone_dir(TC) ->
case TC of
- basic -> otptest;
- resolve -> otptest;
- edns0 -> otptest;
- files_monitor -> otptest;
- last_ms_answer -> otptest;
+ basic -> otptest;
+ resolve -> otptest;
+ edns0 -> otptest;
+ files_monitor -> otptest;
+ last_ms_answer -> otptest;
intermediate_error ->
{internal,
#{rcode => ?REFUSED}};
+ servfail_retry_timeout_default ->
+ {internal,
+ #{rcode => ?SERVFAIL, etd => 1500}};
+ servfail_retry_timeout_1000 ->
+ {internal,
+ #{rcode => ?SERVFAIL, etd => 1000}};
_ -> undefined
end.
init_per_testcase(Func, Config) ->
+
+ ?P("init_per_testcase -> entry with"
+ "~n Func: ~p"
+ "~n Config: ~p", [Func, Config]),
+
PrivDir = proplists:get_value(priv_dir, Config),
DataDir = proplists:get_value(data_dir, Config),
try ns_init(zone_dir(Func), PrivDir, DataDir) of
NsSpec ->
+ ?P("init_per_testcase -> get resolver lookup"),
Lookup = inet_db:res_option(lookup),
+ ?P("init_per_testcase -> set file:dns"),
inet_db:set_lookup([file,dns]),
case NsSpec of
{_,{IP,Port},_} ->
+ ?P("init_per_testcase -> insert alt nameserver ~p:~w",
+ [IP, Port]),
inet_db:ins_alt_ns(IP, Port);
_ -> ok
end,
%% dbg:tracer(),
%% dbg:p(all, c),
%% dbg:tpl(inet_res, query_nss_res, cx),
- [{nameserver,NsSpec},{res_lookup,Lookup}|Config]
+ ?P("init_per_testcase -> done:"
+ "~n NsSpec: ~p"
+ "~n Lookup: ~p", [NsSpec, Lookup]),
+ [{nameserver, NsSpec}, {res_lookup, Lookup} | Config]
catch
SkipReason ->
- {skip,SkipReason}
+ ?P("init_per_testcase -> catched:"
+ "~n SkipReason: ~p", [SkipReason]),
+ {skip, SkipReason}
end.
end_per_testcase(_Func, Config) ->
@@ -129,6 +182,7 @@ end_per_testcase(_Func, Config) ->
%% dbg:stop(),
ns_end(NsSpec, proplists:get_value(priv_dir, Config)).
+
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Nameserver control
@@ -137,12 +191,20 @@ ns(Config) ->
NS.
ns_init(ZoneDir, PrivDir, DataDir) ->
+
+ ?P("ns_init -> entry with"
+ "~n ZoneDir: ~p"
+ "~n PrivDir: ~p"
+ "~n DataDir: ~p", [ZoneDir, PrivDir, DataDir]),
+
case {os:type(),ZoneDir} of
{_,{internal,ServerSpec}} ->
ns_start_internal(ServerSpec);
{{unix,_},undefined} ->
+ ?P("ns_init -> nothing"),
undefined;
{{unix,_},otptest} ->
+ ?P("ns_init -> prepare start"),
PortNum = case {os:type(),os:version()} of
{{unix,solaris},{M,V,_}} when M =< 5, V < 10 ->
11895 + rand:uniform(100);
@@ -152,8 +214,11 @@ ns_init(ZoneDir, PrivDir, DataDir) ->
gen_udp:close(S),
PNum
end,
+ ?P("ns_init -> use port number ~p", [PortNum]),
RunNamed = filename:join(DataDir, ?RUN_NAMED),
+ ?P("ns_init -> use named ~p", [RunNamed]),
NS = {{127,0,0,1},PortNum},
+ ?P("ns_init -> try open port (exec)"),
P = erlang:open_port({spawn_executable,RunNamed},
[{cd,PrivDir},
{line,80},
@@ -162,25 +227,39 @@ ns_init(ZoneDir, PrivDir, DataDir) ->
atom_to_list(ZoneDir)]},
stderr_to_stdout,
eof]),
+ ?P("ns_init -> port ~p", [P]),
ns_start(ZoneDir, PrivDir, NS, P);
_ ->
throw("Only run on Unix")
end.
ns_start(ZoneDir, PrivDir, NS, P) ->
+
+ ?P("ns_start -> await message"),
+
case ns_collect(P) of
eof ->
+ ?P("ns_start -> eof"),
erlang:error(eof);
"Running: "++_ ->
+ ?P("ns_start -> running"),
{ZoneDir,NS,P};
"Error: "++Error ->
+ ?P("ns_start -> error: "
+ "~n ~p", [Error]),
ns_printlog(filename:join([PrivDir,ZoneDir,"named.log"])),
throw(Error);
- _ ->
+ _X ->
+ ?P("ns_start -> retry"),
ns_start(ZoneDir, PrivDir, NS, P)
end.
+
ns_start_internal(ServerSpec) ->
+
+ ?P("ns_start_internal -> entry with"
+ "~n ServerSpec: ~p", [ServerSpec]),
+
Parent = self(),
Tag = make_ref(),
{P,Mref} =
@@ -197,9 +276,12 @@ ns_start_internal(ServerSpec) ->
end),
receive
{Tag,_NS,P} = NsSpec ->
+ ?P("ns_start_internal -> ~p started", [P]),
demonitor(Mref, [flush]),
NsSpec;
{'DOWN',Mref,_,_,Reason} ->
+ ?P("ns_start_internal -> failed start:"
+ "~n ~p", [Reason]),
exit({ns_start_internal,Reason})
end.
@@ -232,7 +314,7 @@ ns_collect(P, Buf) ->
receive
{P,{data,{eol,L}}} ->
Line = lists:flatten(lists:reverse(Buf, [L])),
- io:format("~s", [Line]),
+ ?P("collected: ~s", [Line]),
Line;
{P,{data,{noeol,L}}} ->
ns_collect(P, [L|Buf]);
@@ -241,7 +323,7 @@ ns_collect(P, Buf) ->
end.
ns_printlog(Fname) ->
- io:format("Name server log file contents:~n", []),
+ ?P("Name server log file contents:"),
case file:read_file(Fname) of
{ok,Bin} ->
io:format("~s~n", [Bin]);
@@ -253,39 +335,81 @@ ns_printlog(Fname) ->
%% Internal name server
ns_internal(ServerSpec, Mref, Tag, S) ->
+ ?P("ns-internal -> await message"),
receive
{'DOWN',Mref,_,_,Reason} ->
+ ?P("ns-internal -> received DOWN: "
+ "~n ~p", [Reason]),
exit(Reason);
Tag ->
+ ?P("ns-internal -> received tag: done"),
ok;
{udp,S,IP,Port,Data} ->
+ ?P("ns-internal -> received UDP message"),
Req = ok(inet_dns:decode(Data)),
- Resp = ns_internal(ServerSpec, Req),
+ {Resp, ServerSpec2} = ns_internal(ServerSpec, Req),
RespData = inet_dns:encode(Resp),
_ = ok(gen_udp:send(S, IP, Port, RespData)),
_ = ok(inet:setopts(S, [{active,once}])),
- ns_internal(ServerSpec, Mref, Tag, S)
+ ns_internal(ServerSpec2, Mref, Tag, S)
end.
-ns_internal(#{rcode := Rcode}, Req) ->
- Hdr = inet_dns:msg(Req, header),
+ns_internal(#{rcode := Rcode,
+ ts := TS0,
+ etd := ETD} = ServerSpec, Req) ->
+ ?P("ns-internal -> request received (time validation)"),
+ TS1 = timestamp(),
+ Hdr = inet_dns:msg(Req, header),
Opcode = inet_dns:header(Hdr, opcode),
- Id = inet_dns:header(Hdr, id),
- Rd = inet_dns:header(Hdr, rd),
+ Id = inet_dns:header(Hdr, id),
+ Rd = inet_dns:header(Hdr, rd),
%%
Qdlist = inet_dns:msg(Req, qdlist),
- inet_dns:make_msg(
- [{header,
- inet_dns:make_header(
- [{id,Id},
- {qr,true},
- {opcode,Opcode},
- {aa,true},
- {tc,false},
- {rd,Rd},
- {ra,false},
- {rcode,Rcode}])},
- {qdlist,Qdlist}]).
+ ?P("ns-internal -> time validation: "
+ "~n ETD: ~w"
+ "~n TS1 - TS0: ~w", [ETD, TS1 - TS0]),
+ RC = if ((TS1 - TS0) >= ETD) ->
+ ?P("ns-internal -> time validated"),
+ ?NOERROR;
+ true ->
+ ?P("ns-internal -> time validation failed"),
+ Rcode
+ end,
+ Resp = inet_dns:make_msg(
+ [{header,
+ inet_dns:make_header(
+ [{id, Id},
+ {qr, true},
+ {opcode, Opcode},
+ {aa, true},
+ {tc, false},
+ {rd, Rd},
+ {ra, false},
+ {rcode, RC}])},
+ {qdlist, Qdlist}]),
+ {Resp, ServerSpec#{ts => timestamp()}};
+ns_internal(#{rcode := Rcode} = ServerSpec, Req) ->
+ ?P("ns-internal -> request received"),
+ Hdr = inet_dns:msg(Req, header),
+ Opcode = inet_dns:header(Hdr, opcode),
+ Id = inet_dns:header(Hdr, id),
+ Rd = inet_dns:header(Hdr, rd),
+ %%
+ Qdlist = inet_dns:msg(Req, qdlist),
+ Resp = inet_dns:make_msg(
+ [{header,
+ inet_dns:make_header(
+ [{id,Id},
+ {qr,true},
+ {opcode,Opcode},
+ {aa,true},
+ {tc,false},
+ {rd,Rd},
+ {ra,false},
+ {rcode,Rcode}])},
+ {qdlist,Qdlist}]),
+ {Resp, ServerSpec#{ts => timestamp()}}.
+
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Behaviour modifying nameserver proxy
@@ -297,10 +421,13 @@ proxy_start(TC, {NS,P}) ->
spawn_link(
fun () ->
try proxy_start(TC, NS, P, Parent, Tag)
- catch C:X:Stacktrace ->
- io:format(
- "~w: ~w:~p ~p~n",
- [self(),C,X,Stacktrace])
+ catch
+ C:X:Stacktrace ->
+ ?P("~p Failed starting proxy: "
+ "~n Class: ~w"
+ "~n Error: ~p"
+ "~n Stacktrace: ~p",
+ [self(), C, X, Stacktrace])
end
end),
receive {started,Tag,Port} ->
@@ -369,11 +496,13 @@ proxy_wait({proxy,Pid,_,_}) ->
proxy_ns({proxy,_,_,ProxyNS}) -> ProxyNS.
+
%%
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Lookup an A record with different API functions.
basic(Config) when is_list(Config) ->
+ ?P("begin"),
NS = ns(Config),
Name = "ns.otptest",
NameC = caseflip(Name),
@@ -381,7 +510,7 @@ basic(Config) when is_list(Config) ->
%%
%% nslookup
{ok,Msg1} = inet_res:nslookup(Name, in, a, [NS]),
- io:format("~p~n", [Msg1]),
+ ?P("nslookup with ~p: ~n ~p", [Name, Msg1]),
[RR1] = inet_dns:msg(Msg1, anlist),
IP = inet_dns:rr(RR1, data),
Bin1 = inet_dns:encode(Msg1),
@@ -389,7 +518,7 @@ basic(Config) when is_list(Config) ->
{ok,Msg1} = inet_dns:decode(Bin1),
%% Now with scrambled case
{ok,Msg1b} = inet_res:nslookup(NameC, in, a, [NS]),
- io:format("~p~n", [Msg1b]),
+ ?P("nslookup with ~p: ~n ~p", [NameC, Msg1b]),
[RR1b] = inet_dns:msg(Msg1b, anlist),
IP = inet_dns:rr(RR1b, data),
Bin1b = inet_dns:encode(Msg1b),
@@ -401,7 +530,7 @@ basic(Config) when is_list(Config) ->
%%
%% resolve
{ok,Msg2} = inet_res:resolve(Name, in, a, [{nameservers,[NS]},verbose]),
- io:format("~p~n", [Msg2]),
+ ?P("resolve with ~p: ~n ~p", [Name, Msg2]),
[RR2] = inet_dns:msg(Msg2, anlist),
IP = inet_dns:rr(RR2, data),
Bin2 = inet_dns:encode(Msg2),
@@ -409,7 +538,7 @@ basic(Config) when is_list(Config) ->
{ok,Msg2} = inet_dns:decode(Bin2),
%% Now with scrambled case
{ok,Msg2b} = inet_res:resolve(NameC, in, a, [{nameservers,[NS]},verbose]),
- io:format("~p~n", [Msg2b]),
+ ?P("resolve with ~p: ~n ~p", [NameC, Msg2b]),
[RR2b] = inet_dns:msg(Msg2b, anlist),
IP = inet_dns:rr(RR2b, data),
Bin2b = inet_dns:encode(Msg2b),
@@ -420,22 +549,28 @@ basic(Config) when is_list(Config) ->
=:= tolower(inet_dns:rr(RR2b, domain))),
%%
%% lookup
+ ?P("lookup"),
[IP] = inet_res:lookup(Name, in, a, [{nameservers,[NS]},verbose]),
[IP] = inet_res:lookup(NameC, in, a, [{nameservers,[NS]},verbose]),
%%
%% gethostbyname
+ ?P("gethostbyname"),
{ok,#hostent{h_addr_list=[IP]}} = inet_res:gethostbyname(Name),
{ok,#hostent{h_addr_list=[IP]}} = inet_res:gethostbyname(NameC),
%%
%% getbyname
+ ?P("getbyname"),
{ok,#hostent{h_addr_list=[IP]}} = inet_res:getbyname(Name, a),
{ok,#hostent{h_addr_list=[IP]}} = inet_res:getbyname(NameC, a),
+ ?P("end"),
ok.
+
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Lookup different records using resolve/2..4.
resolve(Config) when is_list(Config) ->
+ ?P("begin"),
Class = in,
NS = ns(Config),
Domain = "otptest",
@@ -474,16 +609,25 @@ resolve(Config) when is_list(Config) ->
{hinfo,{"BEAM","Erlang/OTP"}}],
undefined}
],
+ ?P("resolve -> with edns 0"),
resolve(Class, [{edns,0},{nameservers,[NS]}], L),
+ ?P("resolve -> with edns false"),
resolve(Class, [{edns,false},{nameservers,[NS]}], L),
%% Again, to see ensure the cache does not mess things up
+ ?P("resolve -> with edns 0 (again)"),
resolve(Class, [{edns,0},{nameservers,[NS]}], L),
- resolve(Class, [{edns,false},{nameservers,[NS]}], L).
+ ?P("resolve -> with edns false (again)"),
+ Res = resolve(Class, [{edns,false},{nameservers,[NS]}], L),
+ ?P("resolve -> done: ~p", [Res]),
+ Res.
resolve(_Class, _Opts, []) ->
+ ?P("resolve -> done"),
ok;
resolve(Class, Opts, [{Type,Nm,Answers,Authority}=Q|Qs]) ->
- io:format("Query: ~p~nOptions: ~p~n", [Q,Opts]),
+ ?P("resolve ->"
+ "~n Query: ~p"
+ "~n Options: ~p", [Q, Opts]),
{Name,NameC} =
case erlang:phash2(Q) band 4 of
0 ->
@@ -505,10 +649,13 @@ resolve(Class, Opts, [{Type,Nm,Answers,Authority}=Q|Qs]) ->
true ->
undefined
end,
+ ?P("resolve -> resolve with ~p", [Name]),
{ok,Msg} = inet_res:resolve(Name, Class, Type, Opts),
check_msg(Class, Type, Msg, AnList, NsList),
+ ?P("resolve -> resolve with ~p", [NameC]),
{ok,MsgC} = inet_res:resolve(NameC, Class, Type, Opts),
check_msg(Class, Type, MsgC, AnList, NsList),
+ ?P("resolve -> next"),
resolve(Class, Opts, Qs).
@@ -534,7 +681,9 @@ normalize_answer(Answer) ->
Answer.
check_msg(Class, Type, Msg, AnList, NsList) ->
- io:format("check_msg Type: ~p, Msg: ~p~n.", [Type,Msg]),
+ ?P("check_msg ->"
+ "~n Type: ~p"
+ "~n Msg: ~p", [Type,Msg]),
case {normalize_answers(
[begin
Class = inet_dns:rr(RR, class),
@@ -562,10 +711,12 @@ check_msg(Class, Type, Msg, AnList, NsList) ->
{ok,Msg} = inet_dns:decode(Buf),
ok.
+
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Test EDNS and truncation.
edns0(Config) when is_list(Config) ->
+ ?P("begin"),
NS = ns(Config),
Domain = "otptest",
Filler = "-5678901234567890123456789012345678.",
@@ -600,52 +751,66 @@ edns0(Config) when is_list(Config) ->
MXs = lists:sort(inet_res_filter(inet_dns:msg(Msg2, anlist), in, mx)),
Buf2 = inet_dns:encode(Msg2),
{ok,Msg2} = inet_dns:decode(Buf2),
- case [RR || RR <- inet_dns:msg(Msg2, arlist),
- inet_dns:rr(RR, type) =:= opt] of
- [OptRR] ->
- io:format("~p~n", [inet_dns:rr(OptRR)]),
- ok;
- [] ->
- case os:type() of
- {unix,sunos} ->
- case os:version() of
- {M,V,_} when M < 5; M == 5, V =< 8 ->
- %% In our test park only known platform
- %% with an DNS resolver that cannot do
- %% EDNS0.
- {comment,"No EDNS0"}
- end
- end
- end.
+ Res = case [RR || RR <- inet_dns:msg(Msg2, arlist),
+ inet_dns:rr(RR, type) =:= opt] of
+ [OptRR] ->
+ ?P("opt rr:"
+ "~n ~p", [inet_dns:rr(OptRR)]),
+ ok;
+ [] ->
+ case os:type() of
+ {unix,sunos} ->
+ case os:version() of
+ {M,V,_} when M < 5; M == 5, V =< 8 ->
+ %% In our test park only known platform
+ %% with an DNS resolver that cannot do
+ %% EDNS0.
+ {comment,"No EDNS0"}
+ end;
+ _ ->
+ ok
+ end
+ end,
+ ?P("done"),
+ Res.
inet_res_filter(Anlist, Class, Type) ->
[inet_dns:rr(RR, data) || RR <- Anlist,
inet_dns:rr(RR, type) =:= Type,
inet_dns:rr(RR, class) =:= Class].
+
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Tests TXT records.
txt_record(Config) when is_list(Config) ->
+ ?P("begin"),
D1 = "cslab.ericsson.net",
D2 = "mail1.cslab.ericsson.net",
+ ?P("try nslookup of ~p", [D1]),
{ok,#dns_rec{anlist=[RR1]}} =
inet_res:nslookup(D1, in, txt),
- io:format("~p~n", [RR1]),
+ ?P("RR1:"
+ "~n ~p", [RR1]),
+ ?P("try nslookup of ~p", [D2]),
{ok,#dns_rec{anlist=[RR2]}} =
inet_res:nslookup(D2, in, txt),
- io:format("~p~n", [RR2]),
+ ?P("RR2:"
+ "~n ~p", [RR2]),
#dns_rr{domain=D1, class=in, type=txt, data=A1} = RR1,
#dns_rr{domain=D2, class=in, type=txt, data=A2} = RR2,
case [lists:flatten(A2)] of
A1 = [[_|_]] -> ok
end,
+ ?P("done"),
ok.
+
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Tests monitoring of /etc/hosts and /etc/resolv.conf, but not them.
files_monitor(Config) when is_list(Config) ->
+ ?P("begin"),
Search = inet_db:res_option(search),
HostsFile = inet_db:res_option(hosts_file),
ResolvConf = inet_db:res_option(resolv_conf),
@@ -656,12 +821,14 @@ files_monitor(Config) when is_list(Config) ->
inet_db:res_option(resolv_conf, ResolvConf),
inet_db:res_option(hosts_file, HostsFile),
inet_db:res_option(inet6, Inet6)
- end.
+ end,
+ ?P("done"),
+ ok.
do_files_monitor(Config) ->
Dir = proplists:get_value(priv_dir, Config),
{ok,Hostname} = inet:gethostname(),
- io:format("Hostname = ~p.~n", [Hostname]),
+ ?P("Hostname: ~p", [Hostname]),
FQDN =
case inet_db:res_option(domain) of
"" ->
@@ -669,7 +836,7 @@ do_files_monitor(Config) ->
_ ->
Hostname++"."++inet_db:res_option(domain)
end,
- io:format("FQDN = ~p.~n", [FQDN]),
+ ?P("FQDN: ~p", [FQDN]),
HostsFile = filename:join(Dir, "files_monitor_hosts"),
ResolvConf = filename:join(Dir, "files_monitor_resolv.conf"),
ok = inet_db:res_option(resolv_conf, ResolvConf),
@@ -750,23 +917,62 @@ last_ms_answer(Config) when is_list(Config) ->
proxy_wait(PSpec),
ok.
+
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% First name server answers ?REFUSED, second does not answer.
%% Check that we get the error code from the first server.
intermediate_error(Config) when is_list(Config) ->
- NS = ns(Config),
- Name = "ns.otptest",
- IP = {127,0,0,1},
+ NS = ns(Config),
+ Name = "ns.otptest",
+ Class = in,
+ Type = a,
+ IP = {127,0,0,1},
%% A "name server" that does not respond
- S = ok(gen_udp:open(0, [{ip,IP},{active,false}])),
- Port = ok(inet:port(S)),
- NSs = [NS,{IP,Port}],
- {error,{refused,_}} =
- inet_res:resolve(Name, in, a, [{nameservers,NSs},verbose], 500),
+ S = ok(gen_udp:open(0, [{ip,IP},{active,false}])),
+ Port = ok(inet:port(S)),
+ NSs = [NS,{IP,Port}],
+ Opts = [{nameservers, NSs}, verbose],
+ Timeout = 500,
+ {error, {refused,_}} = inet_res:resolve(Name, Class, Type, Opts, Timeout),
_ = gen_udp:close(S),
ok.
+
+%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% A name server that firstanswers ?SERVFAIL, the second try *if* the retry
+%% is not received *too soon* (etd) answers noerror.
+
+servfail_retry_timeout_default(Config) when is_list(Config) ->
+ NS = ns(Config),
+ Name = "ns.otptest",
+ Class = in,
+ Type = a,
+ Opts = [{nameservers,[NS]}, verbose],
+ ?P("try resolve"),
+ {ok, Rec} = inet_res:resolve(Name, Class, Type, Opts),
+ ?P("resolved: "
+ "~n ~p", [Rec]),
+ ok.
+
+
+%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% A name server that firstanswers ?SERVFAIL, the second try *if* the retry
+%% is not received *too soon* (etd) answers noerror.
+
+servfail_retry_timeout_1000(Config) when is_list(Config) ->
+ NS = ns(Config),
+ Name = "ns.otptest",
+ Class = in,
+ Type = a,
+ Opts = [{nameservers,[NS]}, {servfail_retry_timeout, 1000}, verbose],
+ ?P("try resolve"),
+ {ok, Rec} = inet_res:resolve(Name, Class, Type, Opts),
+ ?P("resolved: "
+ "~n ~p", [Rec]),
+ ok.
+
+
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Compatibility tests. Call the inet_SUITE tests, but with
%% lookup = [file,dns] instead of [native]
@@ -789,6 +995,9 @@ host_and_addr() -> inet_SUITE:host_and_addr().
host_and_addr(Config) -> inet_SUITE:host_and_addr(Config).
+timestamp() ->
+ erlang:monotonic_time(milli_seconds).
+
%% Case flip helper
diff --git a/lib/kernel/test/kernel_test_lib.erl b/lib/kernel/test/kernel_test_lib.erl
index 867e7775d6..a0d62023f2 100644
--- a/lib/kernel/test/kernel_test_lib.erl
+++ b/lib/kernel/test/kernel_test_lib.erl
@@ -54,7 +54,7 @@ init_per_suite(AllowSkip, Config) when is_boolean(AllowSkip) ->
true ->
{skip, "Unstable host and/or os (or combo thererof)"};
false ->
- [{gen_inet_factor, Factor} | Config]
+ [{kernel_factor, Factor} | Config]
catch
throw:{skip, _} = SKIP ->
SKIP
@@ -62,7 +62,7 @@ init_per_suite(AllowSkip, Config) when is_boolean(AllowSkip) ->
{Factor, _HostInfo} when (AllowSkip =:= false) andalso
is_integer(Factor) ->
- [{gen_inet_factor, Factor} | Config]
+ [{kernel_factor, Factor} | Config]
catch
throw:{skip, _} = SKIP ->
diff --git a/lib/kernel/test/logger_SUITE.erl b/lib/kernel/test/logger_SUITE.erl
index 9f2e0e2f3b..8513cf2936 100644
--- a/lib/kernel/test/logger_SUITE.erl
+++ b/lib/kernel/test/logger_SUITE.erl
@@ -68,7 +68,7 @@ init_per_testcase(_TestCase, Config) ->
[{logger_config,PC}|Config].
end_per_testcase(Case, Config) ->
- try apply(?MODULE,Case,[cleanup,Config])
+ try erlang:apply(?MODULE,Case,[cleanup,Config])
catch error:undef -> ok
end,
ok.
@@ -1408,3 +1408,9 @@ check_config(crash) ->
erlang:error({badmatch,3});
check_config(_) ->
ok.
+
+%% this function is also a test. When logger.hrl used non-qualified
+%% apply/3 call, any module that was implementing apply/3 could
+%% not use any logging macro
+apply(_Any, _Any, _Any) ->
+ ok.
diff --git a/lib/kernel/test/os_SUITE_data/my_fds.c b/lib/kernel/test/os_SUITE_data/my_fds.c
index 704a4d1e1d..8b1ce13822 100644
--- a/lib/kernel/test/os_SUITE_data/my_fds.c
+++ b/lib/kernel/test/os_SUITE_data/my_fds.c
@@ -1,5 +1,9 @@
#include <stdio.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
int
main(int argc, char** argv)
{
diff --git a/lib/kernel/test/pg_SUITE.erl b/lib/kernel/test/pg_SUITE.erl
index 725169cda5..f03b8a6a39 100644
--- a/lib/kernel/test/pg_SUITE.erl
+++ b/lib/kernel/test/pg_SUITE.erl
@@ -43,6 +43,7 @@
overlay_missing/0, overlay_missing/1,
single/0, single/1,
two/1,
+ empty_group_by_remote_leave/0, empty_group_by_remote_leave/1,
thundering_herd/0, thundering_herd/1,
initial/1,
netsplit/1,
@@ -101,7 +102,7 @@ groups() ->
{basic, [parallel], [errors, pg, leave_exit_race, single, overlay_missing]},
{performance, [sequential], [thundering_herd]},
{cluster, [parallel], [process_owner_check, two, initial, netsplit, trisplit, foursplit,
- exchange, nolocal, double, scope_restart, missing_scope_join,
+ exchange, nolocal, double, scope_restart, missing_scope_join, empty_group_by_remote_leave,
disconnected_start, forced_sync, group_leave]}
].
@@ -270,7 +271,31 @@ two(Config) when is_list(Config) ->
stop_node(TwoPeer, Socket),
%% hope that 'nodedown' comes before we route our request
sync(?FUNCTION_NAME),
+ ok.
+
+empty_group_by_remote_leave() ->
+ [{doc, "Empty group should be deleted from nodes."}].
+
+empty_group_by_remote_leave(Config) when is_list(Config) ->
+ {TwoPeer, Socket} = spawn_node(?FUNCTION_NAME, ?FUNCTION_NAME),
+ RemoteNode = rpc:call(TwoPeer, erlang, whereis, [?FUNCTION_NAME]),
+ RemotePid = erlang:spawn(TwoPeer, forever()),
+ % remote join
+ ?assertEqual(ok, rpc:call(TwoPeer, pg, join, [?FUNCTION_NAME, ?FUNCTION_NAME, RemotePid])),
+ sync({?FUNCTION_NAME, TwoPeer}),
+ ?assertEqual([RemotePid], pg:get_members(?FUNCTION_NAME, ?FUNCTION_NAME)),
+ % inspecting internal state is not best practice, but there's no other way to check if the state is correct.
+ {state, _, _, #{RemoteNode := {_, RemoteMap}}} = sys:get_state(?FUNCTION_NAME),
+ ?assertEqual(#{?FUNCTION_NAME => [RemotePid]}, RemoteMap),
+ % remote leave
+ ?assertEqual(ok, rpc:call(TwoPeer, pg, leave, [?FUNCTION_NAME, ?FUNCTION_NAME, RemotePid])),
+ sync({?FUNCTION_NAME, TwoPeer}),
?assertEqual([], pg:get_members(?FUNCTION_NAME, ?FUNCTION_NAME)),
+ {state, _, _, #{RemoteNode := {_, NewRemoteMap}}} = sys:get_state(?FUNCTION_NAME),
+ % empty group should be deleted.
+ ?assertEqual(#{}, NewRemoteMap),
+
+ stop_node(TwoPeer, Socket),
ok.
thundering_herd() ->
diff --git a/lib/kernel/test/prim_file_SUITE.erl b/lib/kernel/test/prim_file_SUITE.erl
index 2f465a15bc..11c3bb15ae 100644
--- a/lib/kernel/test/prim_file_SUITE.erl
+++ b/lib/kernel/test/prim_file_SUITE.erl
@@ -30,7 +30,8 @@
file_read_file_info_opts/1, file_write_file_info_opts/1,
file_write_read_file_info_opts/1]).
-export([rename/1, access/1, truncate/1, datasync/1, sync/1,
- read_write/1, pread_write/1, append/1, exclusive/1]).
+ read_write/1, pread_write/1, append/1, exclusive/1,
+ read_file_rename_race/1]).
-export([e_delete/1, e_rename/1, e_make_dir/1, e_del_dir/1]).
-export([make_link/1, read_link_info_for_non_link/1,
@@ -67,7 +68,7 @@ groups() ->
truncate, sync, datasync, advise, large_write, allocate]},
{open, [],
[open1, modes, close, access, read_write, pread_write,
- append, exclusive]},
+ append, exclusive, read_file_rename_race]},
{pos, [], [pos1, pos2]},
{file_info, [],
[file_info_basic_file,file_info_basic_directory, file_info_bad,
@@ -568,6 +569,60 @@ exclusive(Config) when is_list(Config) ->
ok = ?PRIM_FILE:close(Fd),
ok.
+%% Test read_file with concurrent renames and size changes.
+
+-define(RFRR_DATA1, <<"gazonk">>).
+-define(RFRR_DATA2, <<"fubar">>). % shorter than and not a prefix of DATA1
+
+read_file_rename_race(Config) when is_list(Config) ->
+ %% This test reportedly fails on Windows and Darwin 9.8.0.
+ Supported =
+ case os:type() of
+ {win32, _} -> false;
+ {unix, darwin} -> os:version() > {9,8,0};
+ {unix, _} -> true
+ end,
+ if Supported ->
+ Dir = proplists:get_value(priv_dir, Config),
+ Name = filename:join(Dir, "filename"),
+ rfrr_write_file(Name, ?RFRR_DATA2),
+ Mutator = spawn_link(fun() -> rfrr_mutator(Name, ?RFRR_DATA1, ?RFRR_DATA2) end),
+ Result = rfrr_reader(Name, 1, _N = 5000),
+ unlink(Mutator),
+ exit(Mutator, kill),
+ ok = Result;
+ true ->
+ {skipped, "Not supported on Windows, or Darwin =< 9.8.0"}
+ end.
+
+rfrr_reader(Name, I, N) when I < N ->
+ case rfrr_read_file(Name, I) of
+ ok -> rfrr_reader(Name, I + 1, N);
+ error -> error
+ end;
+rfrr_reader(_Name, _I, _N) -> ok.
+
+rfrr_read_file(Name, I) ->
+ case prim_file:read_file(Name) of
+ {ok, ?RFRR_DATA1} -> ok;
+ {ok, ?RFRR_DATA2} -> ok;
+ Other ->
+ io:format(standard_error, "rfrr_read_file #~p got ~p\n", [I, Other]),
+ error
+ end.
+
+%% Correctness of read_file must not depend on the mutator using the
+%% file server in the reader's Erlang VM, so we use prim_file here.
+rfrr_mutator(Name, Data1, Data2) ->
+ rfrr_write_file(Name, Data1),
+ rfrr_mutator(Name, Data2, Data1).
+
+%% Atomically replace Name with a new file containing Data.
+rfrr_write_file(Name, Data) ->
+ NameTmp = Name ++ ".tmp", % must be in same volume as Name
+ ok = prim_file:write_file(NameTmp, Data),
+ ok = prim_file:rename(NameTmp, Name).
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/lib/kernel/vsn.mk b/lib/kernel/vsn.mk
index 19870f9090..551e255586 100644
--- a/lib/kernel/vsn.mk
+++ b/lib/kernel/vsn.mk
@@ -1 +1 @@
-KERNEL_VSN = 7.1
+KERNEL_VSN = 7.2
diff --git a/lib/megaco/configure.in b/lib/megaco/configure.in
index bf22776ae0..789fbbed04 100644
--- a/lib/megaco/configure.in
+++ b/lib/megaco/configure.in
@@ -25,12 +25,7 @@ dnl define([AC_CACHE_SAVE], )dnl
AC_INIT(vsn.mk)
-if test -z "$ERL_TOP" || test ! -d $ERL_TOP ; then
- AC_CONFIG_AUX_DIRS(autoconf)
-else
- erl_top=${ERL_TOP}
- AC_CONFIG_AUX_DIRS($erl_top/erts/autoconf)
-fi
+AC_CONFIG_AUX_DIRS(${ERL_TOP}/erts/autoconf)
if test "X$host" != "Xfree_source" -a "X$host" != "Xwin32"; then
AC_CANONICAL_HOST
diff --git a/lib/megaco/doc/src/notes.xml b/lib/megaco/doc/src/notes.xml
index 738ea02ed7..7ea065ff72 100644
--- a/lib/megaco/doc/src/notes.xml
+++ b/lib/megaco/doc/src/notes.xml
@@ -37,7 +37,39 @@
section is the version number of Megaco.</p>
- <section><title>Megaco 3.19.3</title>
+ <section><title>Megaco 3.19.5</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed usage of <c>AC_CONFIG_AUX_DIRS()</c> macros in
+ configure script sources.</p>
+ <p>
+ Own Id: OTP-17093 Aux Id: ERL-1447, PR-2948 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Megaco 3.19.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Empty statistics descriptor (now) allowed in both encode
+ and decode for version 3.</p>
+ <p>
+ Own Id: OTP-17012 Aux Id: ERL-1405 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Megaco 3.19.3</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
@@ -109,6 +141,22 @@
</section>
+ <section><title>Megaco 3.18.8.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Empty statistics descriptor (now) allowed in both encode
+ and decode for version 3.</p>
+ <p>
+ Own Id: OTP-17012 Aux Id: ERL-1405 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Megaco 3.18.8.2</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/megaco/src/text/megaco_text_gen_v3.hrl b/lib/megaco/src/text/megaco_text_gen_v3.hrl
index e440ec6651..35500b521e 100644
--- a/lib/megaco/src/text/megaco_text_gen_v3.hrl
+++ b/lib/megaco/src/text/megaco_text_gen_v3.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2019. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -1152,7 +1152,9 @@ enc_ammDescriptor({Tag, Desc}, State) ->
statisticsDescriptor -> enc_StatisticsDescriptor(Desc, State);
_ ->
error({invalid_ammDescriptor_tag, Tag})
- end.
+ end;
+enc_ammDescriptor(Tag, State) when is_atom(Tag) ->
+ enc_ammDescriptor({Tag, []}, State).
enc_AmmsReply(#'AmmsReply'{terminationID = TIDs,
terminationAudit = asn1_NOVALUE}, State) ->
@@ -3208,14 +3210,20 @@ enc_PackagesItem(Val, State)
enc_StatisticsDescriptor({'StatisticsDescriptor',Val}, State) ->
enc_StatisticsDescriptor(Val, State);
-enc_StatisticsDescriptor(List, State) when is_list(List) ->
+enc_StatisticsDescriptor(List, State)
+ when is_list(List) andalso (List =/= []) ->
[
?StatsToken,
?LBRKT_INDENT(State),
enc_list([{List, fun enc_StatisticsParameter/2}], ?INC_INDENT(State)),
?RBRKT_INDENT(State)
+ ];
+enc_StatisticsDescriptor([], _State) ->
+ [
+ ?StatsToken
].
+
enc_StatisticsParameter(Val, State)
when is_record(Val, 'StatisticsParameter') ->
PkgdName = ?META_ENC(statistics, Val#'StatisticsParameter'.statName),
diff --git a/lib/megaco/src/text/megaco_text_parser_v3.hrl b/lib/megaco/src/text/megaco_text_parser_v3.hrl
index 7b8fff2080..9d0afaa11b 100644
--- a/lib/megaco/src/text/megaco_text_parser_v3.hrl
+++ b/lib/megaco/src/text/megaco_text_parser_v3.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2019. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -1740,6 +1740,36 @@ do_merge_control_streamParms([{SubTag, SD} | T] = All, LCD) ->
do_merge_control_streamParms([], LCD) ->
LCD.
+
+%% This is to get around the two defs:
+%%
+%% statisticsDescriptor = StatsToken [LBRKT statisticsParameter *(COMMA
+%% statisticsParameter) RBRKT]
+%%
+%% and
+%%
+%% auditReturnParameter = (mediaDescriptor / modemDescriptor / muxDescriptor /
+%% eventsDescriptor / signalsDescriptor /
+%% digitMapDescriptor / observedEventsDescriptor /
+%% eventBufferDescriptor / statisticsDescriptor /
+%% packagesDescriptor / errorDescriptor /
+%% auditReturnItem)
+%% auditReturnItem = (MuxToken / ModemToken / MediaToken /
+%% DigitMapToken / StatsToken / ObservedEventsToken /
+%% PackagesToken)
+%%
+%% In both statisticsDescriptor and auditReturnItem the StatsToken
+%% can occure on its owm => conflict
+
+-ifdef(megaco_parser_inline).
+-compile({inline,[{ensure_arp_statisticsDescriptor,1}]}).
+-endif.
+ensure_arp_statisticsDescriptor([]) ->
+ {auditReturnItem, statsToken};
+ensure_arp_statisticsDescriptor(SPs) ->
+ {statisticsDescriptor, SPs}.
+
+
-ifdef(megaco_parser_inline).
-compile({inline,[{merge_terminationStateDescriptor,1}]}).
-endif.
diff --git a/lib/megaco/src/text/megaco_text_parser_v3.yrl b/lib/megaco/src/text/megaco_text_parser_v3.yrl
index 3e163f2ac6..4954386e03 100644
--- a/lib/megaco/src/text/megaco_text_parser_v3.yrl
+++ b/lib/megaco/src/text/megaco_text_parser_v3.yrl
@@ -760,12 +760,13 @@ auditReturnParameter -> signalsDescriptor : {signalsDescriptor, '$1'} .
auditReturnParameter -> digitMapDescriptor : {digitMapDescriptor, '$1'} .
auditReturnParameter -> observedEventsDescriptor : {observedEventsDescriptor, '$1'} .
auditReturnParameter -> eventBufferDescriptor : {eventBufferDescriptor, '$1'} .
-auditReturnParameter -> statisticsDescriptor : {statisticsDescriptor, '$1'} .
+%% Conflict with 'empty' statisticsDescriptor and auditReturnItem (see below)
+auditReturnParameter -> statisticsDescriptor : ensure_arp_statisticsDescriptor('$1') .
auditReturnParameter -> packagesDescriptor : {packagesDescriptor, '$1'} .
auditReturnParameter -> errorDescriptor : {errorDescriptor, '$1'} .
auditReturnParameter -> auditReturnItem : {auditReturnItem, '$1'} .
-auditDescriptor -> 'AuditToken' 'LBRKT' auditDescriptorBody 'RBRKT' :
+auditDescriptor -> 'AuditToken' 'LBRKT' auditDescriptorBody 'RBRKT' :
merge_auditDescriptor('$3') .
auditDescriptorBody -> auditItem auditItemList : ['$1' | '$2'].
@@ -780,13 +781,16 @@ auditReturnItem -> 'MuxToken' : muxToken .
auditReturnItem -> 'ModemToken' : modemToken .
auditReturnItem -> 'MediaToken' : mediaToken .
auditReturnItem -> 'DigitMapToken' : digitMapToken .
-auditReturnItem -> 'StatsToken' : statsToken .
+%% Conflict with 'empty' statisticsDescriptor:
+%% auditReturnItem -> 'StatsToken' : statsToken .
auditReturnItem -> 'ObservedEventsToken' : observedEventsToken .
auditReturnItem -> 'PackagesToken' : packagesToken .
%% at-most-once, and DigitMapToken and PackagesToken are not allowed
%% in AuditCapabilities command
auditItem -> auditReturnItem : '$1' .
+%% Moved from ari above (conflict with 'empty' statisticsDescriptor)
+auditItem -> 'StatsToken' : statsToken.
auditItem -> 'SignalsToken' : signalsToken.
auditItem -> 'EventBufferToken' : eventBufferToken.
auditItem -> 'EventsToken' : eventsToken .
@@ -1095,11 +1099,13 @@ localParmList -> '$empty': [] .
terminationStateDescriptor -> 'TerminationStateToken'
'LBRKT' terminationStateParm
- terminationStateParms 'RBRKT'
- : merge_terminationStateDescriptor(['$3' | '$4']) .
+ terminationStateParms 'RBRKT' :
+ merge_terminationStateDescriptor(['$3' | '$4']) .
-terminationStateParms -> 'COMMA' terminationStateParm terminationStateParms : ['$2' | '$3'] .
-terminationStateParms -> '$empty' : [] .
+terminationStateParms -> 'COMMA' terminationStateParm terminationStateParms :
+ ['$2' | '$3'] .
+terminationStateParms -> '$empty' :
+ [] .
%% at-most-once per item except for propertyParm
localParm -> 'ReservedGroupToken' 'EQUAL' onOrOff : {group, '$3'} .
@@ -1504,6 +1510,7 @@ statisticsDescriptor -> 'StatsToken'
'LBRKT' statisticsParameter
statisticsParameters 'RBRKT'
: ['$3' | '$4'] .
+statisticsDescriptor -> 'StatsToken' : [] .
statisticsParameters -> 'COMMA' statisticsParameter statisticsParameters : ['$2' | '$3'] .
statisticsParameters -> '$empty' : [] .
diff --git a/lib/megaco/test/megaco_codec_v3_SUITE.erl b/lib/megaco/test/megaco_codec_v3_SUITE.erl
index 3089030aad..56a92b5f0a 100644
--- a/lib/megaco/test/megaco_codec_v3_SUITE.erl
+++ b/lib/megaco/test/megaco_codec_v3_SUITE.erl
@@ -1,8 +1,8 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2006-2019. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2006-2020. All Rights Reserved.
+%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
@@ -137,8 +137,10 @@
compact_otp16818_msg34/1,
compact_otp16818_msg35/1,
compact_otp16818_msg36/1,
-
- flex_compact_otp4299_msg1/1,
+ compact_erl1405_msg01/1,
+ compact_erl1405_msg02/1,
+
+ flex_compact_otp4299_msg1/1,
flex_compact_otp7431_msg01/1,
flex_compact_otp7431_msg02/1,
flex_compact_otp7431_msg03/1,
@@ -193,16 +195,18 @@
pretty_otp7671_msg04/1,
pretty_otp7671_msg05/1,
pretty_otp8114_msg01/1,
-
- flex_pretty_otp5042_msg1/1,
- flex_pretty_otp5085_msg1/1,
- flex_pretty_otp5085_msg2/1,
- flex_pretty_otp5085_msg3/1,
- flex_pretty_otp5085_msg4/1,
- flex_pretty_otp5085_msg5/1,
- flex_pretty_otp5085_msg6/1,
- flex_pretty_otp5085_msg7/1,
- flex_pretty_otp5085_msg8/1,
+ pretty_erl1405_msg01/1,
+ pretty_erl1405_msg02/1,
+
+ flex_pretty_otp5042_msg1/1,
+ flex_pretty_otp5085_msg1/1,
+ flex_pretty_otp5085_msg2/1,
+ flex_pretty_otp5085_msg3/1,
+ flex_pretty_otp5085_msg4/1,
+ flex_pretty_otp5085_msg5/1,
+ flex_pretty_otp5085_msg6/1,
+ flex_pretty_otp5085_msg7/1,
+ flex_pretty_otp5085_msg8/1,
flex_pretty_otp5600_msg1/1,
flex_pretty_otp5600_msg2/1,
flex_pretty_otp5601_msg1/1,
@@ -406,7 +410,6 @@ compact_tickets_cases() ->
compact_otp6017_msg01,
compact_otp6017_msg02,
compact_otp6017_msg03,
-
compact_otp16818_msg01,
compact_otp16818_msg02,
compact_otp16818_msg03,
@@ -430,7 +433,9 @@ compact_tickets_cases() ->
compact_otp16818_msg33,
compact_otp16818_msg34,
compact_otp16818_msg35,
- compact_otp16818_msg36
+ compact_otp16818_msg36,
+ compact_erl1405_msg01,
+ compact_erl1405_msg02
].
flex_compact_tickets_cases() ->
@@ -492,7 +497,9 @@ pretty_tickets_cases() ->
pretty_otp7671_msg03,
pretty_otp7671_msg04,
pretty_otp7671_msg05,
- pretty_otp8114_msg01
+ pretty_otp8114_msg01,
+ pretty_erl1405_msg01,
+ pretty_erl1405_msg02
].
flex_pretty_tickets_cases() ->
@@ -2222,7 +2229,6 @@ compact_otp6017_msg(CID) when is_integer(CID) ->
"{SC=root{SV{MT=RS,RE=901}}}}".
-
%% --------------------------------------------------------------
%%
compact_otp16818_msg01(suite) ->
@@ -2554,6 +2560,62 @@ compact_otp16818_msg(X) when is_list(X) ->
"] T=2523{C=-{SC=ROOT{SV{MT=RS,RE=901,PF=ETSI_BGF/2,V=3}}}}".
+%% --------------------------------------------------------------
+
+compact_erl1405_msg01(suite) ->
+ [];
+compact_erl1405_msg01(Config) when is_list(Config) ->
+ put(severity,trc),
+ put(dbg,true),
+ d("compact_erl1405_msg01 -> entry", []),
+ ?ACQUIRE_NODES(1, Config),
+ ok = compact_erl1405(statisticsDescriptor),
+ erase(severity),
+ erase(dbg),
+ ok.
+
+compact_erl1405_msg02(suite) ->
+ [];
+compact_erl1405_msg02(Config) when is_list(Config) ->
+ put(severity,trc),
+ put(dbg,true),
+ d("compact_erl1405_msg02 -> entry", []),
+ ?ACQUIRE_NODES(1, Config),
+ ok = compact_erl1405({statisticsDescriptor, []}),
+ erase(severity),
+ erase(dbg),
+ ok.
+
+compact_erl1405(Descriptor) ->
+ ticket_compact_encode_decode_ok( erl1405_msg(Descriptor) ).
+
+erl1405_msg(Descriptor) ->
+ #'MegacoMessage'{
+ mess = #'Message'{
+ version = ?VERSION,
+ mId = {deviceName, "test"},
+ messageBody = {transactions, [
+ {transactionRequest, #'TransactionRequest'{
+ transactionId = 1,
+ actions = [
+ #'ActionRequest'{
+ contextId = ?megaco_choose_context_id,
+ commandRequests = [
+ #'CommandRequest'{
+ command = {addReq, #'AmmRequest'{
+ terminationID = [#megaco_term_id{id = ["test"]}],
+ descriptors = [Descriptor]
+ }}
+ }
+ ]
+ }
+ ]
+ }}
+ ]}
+ }
+ }.
+
+
%% ==============================================================
%%
%% F l e x C o m p a c t T e s t c a s e s
@@ -4352,7 +4414,6 @@ cmp_otp7671_msg05(#'MegacoMessage'{authHeader = asn1_NOVALUE,
%% --------------------------------------------------------------
%%
-
pretty_otp8114_msg01(suite) ->
[];
pretty_otp8114_msg01(Config) when is_list(Config) ->
@@ -4437,6 +4498,38 @@ otp8114(InitialMessage, Codec, Conf) ->
megaco_codec_test_lib:expect_exec(Instructions, InitialData).
+
+%% --------------------------------------------------------------
+%%
+
+pretty_erl1405_msg01(suite) ->
+ [];
+pretty_erl1405_msg01(Config) when is_list(Config) ->
+ put(severity,trc),
+ put(dbg,true),
+ d("pretty_erl1405_msg01 -> entry", []),
+ ?ACQUIRE_NODES(1, Config),
+ ok = pretty_erl1405(statisticsDescriptor),
+ erase(severity),
+ erase(dbg),
+ ok.
+
+pretty_erl1405_msg02(suite) ->
+ [];
+pretty_erl1405_msg02(Config) when is_list(Config) ->
+ put(severity,trc),
+ put(dbg,true),
+ d("pretty_erl1405_msg02 -> entry", []),
+ ?ACQUIRE_NODES(1, Config),
+ ok = pretty_erl1405({statisticsDescriptor, []}),
+ erase(severity),
+ erase(dbg),
+ ok.
+
+pretty_erl1405(Descriptor) ->
+ ticket_pretty_encode_decode_ok( erl1405_msg(Descriptor) ).
+
+
%% ==============================================================
%%
%% F l e x P r e t t y T e s t c a s e s
@@ -5197,7 +5290,7 @@ msgs7(Encoding) ->
{msg78a07, msg78a07(), Plain, [{dbg,false}],[text,binary,erlang]},
{msg78a08, msg78a08(), Plain, [{dbg,false}],[text,binary,erlang]},
{msg78a09, msg78a09(), Plain, [{dbg,false}],[text,binary,erlang]},
- {msg79a01, msg79a01(), Plain, [{dbg,false}],[text,binary,erlang]}
+ {msg79a01, msg79a01(), Plain, [{dbg,true}],[text,binary,erlang]}
],
[{N,M,F,C}||{N,M,F,C,E} <- Msgs,lists:member(Encoding,E)].
diff --git a/lib/megaco/test/megaco_test_lib.erl b/lib/megaco/test/megaco_test_lib.erl
index 5caba6933b..49168ea565 100644
--- a/lib/megaco/test/megaco_test_lib.erl
+++ b/lib/megaco/test/megaco_test_lib.erl
@@ -28,6 +28,8 @@
%% -compile(export_all).
+-compile({no_auto_import, [error/3]}).
+
-export([
proxy_call/3,
log/4,
diff --git a/lib/megaco/test/megaco_test_msg_v3_lib.erl b/lib/megaco/test/megaco_test_msg_v3_lib.erl
index 5264791370..8bb821b475 100644
--- a/lib/megaco/test/megaco_test_msg_v3_lib.erl
+++ b/lib/megaco/test/megaco_test_msg_v3_lib.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -3775,8 +3775,16 @@ is_AmmRequest_descriptors(Descs) ->
is_AmmRequest_descriptors([], _) ->
true;
-is_AmmRequest_descriptors([{Tag, _} = Desc|Descs], FoundDescs) ->
- d("is_AmmRequest_descriptors -> entry with"
+is_AmmRequest_descriptors([Desc|Descs], FoundDescs) ->
+ FoundDescs2 = is_AmmRequest_descriptor(Desc, FoundDescs),
+ is_AmmRequest_descriptors(Descs, FoundDescs2);
+is_AmmRequest_descriptors(Descs, _) ->
+ d("is_AmmRequest_descriptors -> entry with WRONG TYPE"
+ "~n Descs: ~p", [Descs]),
+ wrong_type('AmmRequest_descriptors', Descs).
+
+is_AmmRequest_descriptor({Tag, _} = Desc, FoundDescs) when is_atom(Tag) ->
+ d("is_AmmRequest_descriptor -> entry with"
"~n Tag: ~p"
"~n FoundDescs: ~p", [Tag, FoundDescs]),
case lists:member(Tag, FoundDescs) of
@@ -3785,15 +3793,13 @@ is_AmmRequest_descriptors([{Tag, _} = Desc|Descs], FoundDescs) ->
false ->
case is_AmmDescriptor(Desc) of
true ->
- is_AmmRequest_descriptors(Descs, [Tag|FoundDescs]);
+ [Tag|FoundDescs];
false ->
wrong_type('AmmRequest_descriptors', Desc)
end
end;
-is_AmmRequest_descriptors(Descs, _) ->
- d("is_AmmRequest_descriptors -> entry with WRONG TYPE"
- "~n Descs: ~p", [Descs]),
- wrong_type('AmmRequest_descriptors', Descs).
+is_AmmRequest_descriptor(Tag, FoundDescs) when is_atom(Tag) ->
+ is_AmmRequest_descriptor({Tag, []}, FoundDescs).
chk_AmmRequest(R, R) when is_record(R, 'AmmRequest') ->
@@ -3837,7 +3843,11 @@ chk_AmmRequest_descriptors([H|T1], [H|T2]) ->
wrong_type('AmmRequest_descriptors_val', H)
end;
chk_AmmRequest_descriptors([H1|T1], [H2|T2]) ->
- d("chk_AmmRequest_descriptors -> entry when not equal"),
+ d("chk_AmmRequest_descriptors -> entry when not equal: "
+ "~n H1: ~p"
+ "~n T1: ~p"
+ "~n H2: ~p"
+ "~n T2: ~p", [H1, T1, H2, T2]),
validate(fun() -> chk_AmmDescriptor(H1, H2) end,
'AmmRequest_descriptors_val'),
chk_AmmRequest_descriptors(T1, T2);
@@ -3885,6 +3895,11 @@ is_AmmDescriptor_val(statisticsDescriptor, D) ->
chk_AmmDescriptor(D, D) ->
chk_type(fun is_AmmDescriptor_tag/1, 'AmmDescriptor', D);
+%% There are two ways of spec an empty statisticsDescriptor:
+%% * statisticsDescriptor
+%% * {statisticsDescriptor, []}
+chk_AmmDescriptor(Tag, {Tag, []}) when (Tag =:= statisticsDescriptor) ->
+ ok;
chk_AmmDescriptor({Tag, Val1} = Cmd1, {Tag, Val2} = Cmd2) ->
case (is_AmmDescriptor_tag(Tag) andalso
is_AmmDescriptor_val(Tag, Val1) andalso
diff --git a/lib/megaco/test/megaco_trans_SUITE.erl b/lib/megaco/test/megaco_trans_SUITE.erl
index 1e281987b8..78ba1f0515 100644
--- a/lib/megaco/test/megaco_trans_SUITE.erl
+++ b/lib/megaco/test/megaco_trans_SUITE.erl
@@ -84,7 +84,8 @@
-define(MG, megaco_test_mg).
-define(MGC, megaco_test_mgc).
--define(MGC_START(Pid, Mid, ET, Verb), ?MGC:start(Pid, Mid, ET, Verb)).
+-define(MGC_START(Pid, Mid, ET, Verb),
+ mgc_start(Pid, Mid, ET, Verb)).
-define(MGC_STOP(Pid), ?MGC:stop(Pid)).
-define(MGC_GET_STATS(Pid, No), ?MGC:get_stats(Pid, No)).
-define(MGC_RESET_STATS(Pid), ?MGC:reset_stats(Pid)).
@@ -99,8 +100,8 @@
-define(MGC_ACK_INFO(Pid,To), ?MGC:ack_info(Pid,To)).
-define(MGC_REQ_INFO(Pid,To), ?MGC:req_info(Pid,To)).
--define(MG_START(Pid, Mid, Enc, Transp, Conf, Verb),
- ?MG:start(Pid, Mid, Enc, Transp, Conf, Verb)).
+-define(MG_START(Pid, Mid, Enc, Transp, Conf, Verb),
+ mg_start(Pid, Mid, Enc, Transp, Conf, Verb)).
-define(MG_STOP(Pid), ?MG:stop(Pid)).
-define(MG_GET_STATS(Pid), ?MG:get_stats(Pid)).
-define(MG_RESET_STATS(Pid), ?MG:reset_stats(Pid)).
@@ -330,14 +331,13 @@ do_single_ack([MgcNode, MgNode]) ->
%% Start the MGC and MGs
i("[MGC] start"),
ET = [{text,tcp}, {text,udp}, {binary,tcp}, {binary,udp}],
- {ok, Mgc} =
- ?MGC_START(MgcNode, {deviceName, "ctrl"}, ET, ?MGC_VERBOSITY),
+ Mgc = ?MGC_START(MgcNode, {deviceName, "ctrl"}, ET, ?MGC_VERBOSITY),
i("[MG] start"),
%% MgConf0 = [{MgNode, "mg", text, tcp, ?MG_VERBOSITY}],
MgMid = {deviceName, "mg"},
MgConfig = [{auto_ack, true}, {trans_timer, 5000}, {trans_ack, true}],
- {ok, Mg} = ?MG_START(MgNode, MgMid, text, tcp, MgConfig, ?MG_VERBOSITY),
+ Mg = ?MG_START(MgNode, MgMid, text, tcp, MgConfig, ?MG_VERBOSITY),
d("MG user info: ~p", [?MG_USER_INFO(Mg, all)]),
@@ -412,8 +412,7 @@ do_multi_ack_timeout([MgcNode, MgNode]) ->
%% Start the MGC and MGs
i("[MGC] start"),
ET = [{text,tcp}, {text,udp}, {binary,tcp}, {binary,udp}],
- {ok, Mgc} =
- ?MGC_START(MgcNode, {deviceName, "ctrl"}, ET, ?MGC_VERBOSITY),
+ Mgc = ?MGC_START(MgcNode, {deviceName, "ctrl"}, ET, ?MGC_VERBOSITY),
i("[MG] start"),
%% MgConf0 = [{MgNode, "mg", text, tcp, ?MG_VERBOSITY}],
@@ -422,7 +421,7 @@ do_multi_ack_timeout([MgcNode, MgNode]) ->
{trans_ack, true},
{trans_timer, 10000},
{trans_ack_maxcount, MaxCount + 10}],
- {ok, Mg} = ?MG_START(MgNode, MgMid, text, tcp, MgConfig, ?MG_VERBOSITY),
+ Mg = ?MG_START(MgNode, MgMid, text, tcp, MgConfig, ?MG_VERBOSITY),
d("MG user info: ~p", [?MG_USER_INFO(Mg, all)]),
@@ -496,8 +495,7 @@ do_multi_ack_maxcount([MgcNode, MgNode]) ->
%% Start the MGC and MGs
i("[MGC] start"),
ET = [{text,tcp}, {text,udp}, {binary,tcp}, {binary,udp}],
- {ok, Mgc} =
- ?MGC_START(MgcNode, {deviceName, "ctrl"}, ET, ?MGC_VERBOSITY),
+ Mgc = ?MGC_START(MgcNode, {deviceName, "ctrl"}, ET, ?MGC_VERBOSITY),
i("[MG] start"),
%% MgConf0 = [{MgNode, "mg", text, tcp, ?MG_VERBOSITY}],
@@ -506,7 +504,7 @@ do_multi_ack_maxcount([MgcNode, MgNode]) ->
%% {trans_timer, 120000},
%% {trans_ack_maxcount, MaxCount}
],
- {ok, Mg} = ?MG_START(MgNode, MgMid, text, tcp, MgConfig, ?MG_VERBOSITY),
+ Mg = ?MG_START(MgNode, MgMid, text, tcp, MgConfig, ?MG_VERBOSITY),
d("MG user info: ~p", [?MG_USER_INFO(Mg, all)]),
@@ -9595,6 +9593,46 @@ await_completion(Ids, Timeout) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+mgc_start(Pid, Mid, ET, Verb) ->
+ try ?MGC:start(Pid, Mid, ET, Verb) of
+ {ok, MGC} ->
+ MGC;
+ {error, StartReason} ->
+ e("failed starting mgc (error): "
+ "~n ~p", [StartReason]),
+ ?SKIP({failed_starting, mgc, StartReason})
+ catch
+ exit:{error, timeout} ->
+ e("failed starting mgc (exit): timeout"),
+ ?SKIP({failed_starting, mgc, timeout});
+ exit:{failed_starting, _, StartExitReason} ->
+ e("failed starting mgc (exit): "
+ "~n ~p", [StartExitReason]),
+ ?SKIP({failed_starting, mgc, StartExitReason})
+ end.
+
+
+mg_start(Pid, Mid, Enc, Transp, Conf, Verb) ->
+ try ?MG:start(Pid, Mid, Enc, Transp, Conf, Verb) of
+ {ok, MG} ->
+ MG;
+ {error, Reason} ->
+ e("failed starting mg (error): "
+ "~n ~p", [Reason]),
+ ?SKIP({failed_starting, mgc, Reason})
+ catch
+ exit:{error, timeout} ->
+ e("failed starting mg (exit): timeout"),
+ ?SKIP({failed_starting, mg, timeout});
+ exit:{failed_starting, _, ExitReason} ->
+ e("failed starting mg (exit): "
+ "~n ~p", [ExitReason]),
+ ?SKIP({failed_starting, mg, ExitReason})
+ end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
try_tc(TCName, Pre, Case, Post) ->
try_tc(TCName, "TEST", ?TEST_VERBOSITY, Pre, Case, Post).
@@ -9617,8 +9655,8 @@ p(F, A) ->
"~n " ++ F ++ "~n",
[?FTS(), self() | A]).
-%% e(F) ->
-%% e(F, []).
+e(F) ->
+ e(F, []).
e(F, A) ->
print(error, "ERROR", F, A).
diff --git a/lib/megaco/vsn.mk b/lib/megaco/vsn.mk
index bb461c9c7b..f416a0324a 100644
--- a/lib/megaco/vsn.mk
+++ b/lib/megaco/vsn.mk
@@ -19,6 +19,6 @@
# %CopyrightEnd%
APPLICATION = megaco
-MEGACO_VSN = 3.19.3
+MEGACO_VSN = 3.19.5
PRE_VSN =
APP_VSN = "$(APPLICATION)-$(MEGACO_VSN)$(PRE_VSN)"
diff --git a/lib/mnesia/doc/src/notes.xml b/lib/mnesia/doc/src/notes.xml
index c070a1277f..c8c7c0c0a5 100644
--- a/lib/mnesia/doc/src/notes.xml
+++ b/lib/mnesia/doc/src/notes.xml
@@ -39,7 +39,23 @@
thus constitutes one section in this document. The title of each
section is the version number of Mnesia.</p>
- <section><title>Mnesia 4.18</title>
+ <section><title>Mnesia 4.18.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Avoid potential performance issue, if the input queue to
+ <c>mnesia_tm</c> is long.</p>
+ <p>
+ Own Id: OTP-17066 Aux Id: PR-2889 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Mnesia 4.18</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
@@ -114,6 +130,22 @@
</section>
+ <section><title>Mnesia 4.16.3.1</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Fixed crash during startup, which could happen if a table
+ was deleted on another node.</p>
+ <p>
+ Own Id: OTP-16815 Aux Id: ERIERL-500 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Mnesia 4.16.3</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/mnesia/src/mnesia_dumper.erl b/lib/mnesia/src/mnesia_dumper.erl
index 0a815b709a..1264efeb68 100644
--- a/lib/mnesia/src/mnesia_dumper.erl
+++ b/lib/mnesia/src/mnesia_dumper.erl
@@ -68,7 +68,10 @@ incr_log_writes() ->
Left = mnesia_lib:incr_counter(trans_log_writes_left, -1),
if
Left =:= 0 ->
- adjust_log_writes(true);
+ %% It doesn't matter which process adjusts counters and sends
+ %% cast to a dumper so to avoid potential lag on global:set_lock
+ %% we delegate it to new process
+ spawn(fun() -> adjust_log_writes(true) end);
true ->
ignore
end.
diff --git a/lib/mnesia/vsn.mk b/lib/mnesia/vsn.mk
index 4844e3807d..19a69fb714 100644
--- a/lib/mnesia/vsn.mk
+++ b/lib/mnesia/vsn.mk
@@ -1 +1 @@
-MNESIA_VSN = 4.18
+MNESIA_VSN = 4.18.1
diff --git a/lib/odbc/configure.in b/lib/odbc/configure.in
index f13cd8c901..7b02f0d4df 100644
--- a/lib/odbc/configure.in
+++ b/lib/odbc/configure.in
@@ -25,12 +25,7 @@ dnl define([AC_CACHE_SAVE], )dnl
dnl Process this file with autoconf to produce a configure script.
AC_INIT(c_src/odbcserver.c)
-if test -z "$ERL_TOP" || test ! -d $ERL_TOP ; then
- AC_CONFIG_AUX_DIRS(autoconf)
-else
- erl_top=${ERL_TOP}
- AC_CONFIG_AUX_DIRS($erl_top/erts/autoconf)
-fi
+AC_CONFIG_AUX_DIRS(${ERL_TOP}/erts/autoconf)
if test "X$host" != "Xfree_source" -a "X$host" != "Xwin32"; then
AC_CANONICAL_HOST
diff --git a/lib/odbc/doc/src/notes.xml b/lib/odbc/doc/src/notes.xml
index 891c6a9c31..3533310e51 100644
--- a/lib/odbc/doc/src/notes.xml
+++ b/lib/odbc/doc/src/notes.xml
@@ -32,7 +32,23 @@
<p>This document describes the changes made to the odbc application.
</p>
- <section><title>ODBC 2.13.1</title>
+ <section><title>ODBC 2.13.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed usage of <c>AC_CONFIG_AUX_DIRS()</c> macros in
+ configure script sources.</p>
+ <p>
+ Own Id: OTP-17093 Aux Id: ERL-1447, PR-2948 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>ODBC 2.13.1</title>
<section><title>Improvements and New Features</title>
<list>
diff --git a/lib/odbc/test/README b/lib/odbc/test/README
index 0a8495afbb..5ae6073d9a 100644
--- a/lib/odbc/test/README
+++ b/lib/odbc/test/README
@@ -47,7 +47,7 @@ something like this:
--- Start example of .odbc.ini ----
-[Postgres]
+[PostgresLinux64Ubuntu]
Driver=/usr/lib/psqlodbc.so
Description=Postgres driver
ServerName=myhost
diff --git a/lib/odbc/test/postgres.erl b/lib/odbc/test/postgres.erl
index 1955358206..e055be9544 100644
--- a/lib/odbc/test/postgres.erl
+++ b/lib/odbc/test/postgres.erl
@@ -207,7 +207,7 @@ bit_true_selected() ->
%-------------------------------------------------------------------------
float_min() ->
- 1.79e-307.
+ 5.0e-324.
float_max() ->
1.79e+308.
@@ -215,7 +215,7 @@ create_float_table() ->
" (FIELD float)".
float_underflow() ->
- "1.80e-308".
+ "2.4e-324".
float_overflow() ->
"1.80e+308".
@@ -288,7 +288,7 @@ describe_string() ->
{"str4",{sql_varchar,10}}]}.
describe_floating() ->
- {ok,[{"f",sql_real},{"r",sql_real},{"d",{sql_float,15}}]}.
+ {ok,[{"f",sql_real},{"r",sql_real},{"d",{sql_float,17}}]}.
describe_dec_num() ->
{ok,[{"mydec",{sql_numeric,9,3}},{"mynum",{sql_numeric,9,2}}]}.
diff --git a/lib/odbc/vsn.mk b/lib/odbc/vsn.mk
index a8e78b5a32..c08f40d233 100644
--- a/lib/odbc/vsn.mk
+++ b/lib/odbc/vsn.mk
@@ -1 +1 @@
-ODBC_VSN = 2.13.1
+ODBC_VSN = 2.13.2
diff --git a/lib/os_mon/doc/src/notes.xml b/lib/os_mon/doc/src/notes.xml
index 87a93c8b6d..e4d544cca4 100644
--- a/lib/os_mon/doc/src/notes.xml
+++ b/lib/os_mon/doc/src/notes.xml
@@ -109,6 +109,29 @@
</section>
+<section><title>Os_Mon 2.5.1.1</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ The configuration parameter
+ <c>memsup_improved_system_memory_data</c> has been
+ introduced. It can be used to modify the result returned
+ by <c>memsup:get_system_memory_data()</c>. For more
+ information see the <c>memsup</c> documentation.</p>
+ <p>
+ Note that the configuration parameter is intended to be
+ removed in OTP 24 and the modified result is intended to
+ be used as of OTP 24.</p>
+ <p>
+ Own Id: OTP-16906 Aux Id: ERIERL-532 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Os_Mon 2.5.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -1131,4 +1154,3 @@
<p>This version is identical with 1.7.</p>
</section>
</chapter>
-
diff --git a/lib/public_key/doc/src/notes.xml b/lib/public_key/doc/src/notes.xml
index a203620cee..7cabfed09a 100644
--- a/lib/public_key/doc/src/notes.xml
+++ b/lib/public_key/doc/src/notes.xml
@@ -35,6 +35,21 @@
<file>notes.xml</file>
</header>
+<section><title>Public_Key 1.9.2</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Corrected dialyzer spec for pkix_path_validation/3</p>
+ <p>
+ Own Id: OTP-17069</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Public_Key 1.9.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl
index 6c2fe1e805..4176fce978 100644
--- a/lib/public_key/src/public_key.erl
+++ b/lib/public_key/src/public_key.erl
@@ -961,7 +961,7 @@ pkix_normalize_name(Issuer) ->
%%--------------------------------------------------------------------
-spec pkix_path_validation(Cert::binary()| #'OTPCertificate'{} | atom(),
- CertChain :: [binary()] ,
+ CertChain :: [binary() | #'OTPCertificate'{}] ,
Options :: [{atom(),term()}]) ->
{ok, {PublicKeyInfo :: term(),
PolicyTree :: term()}} |
diff --git a/lib/public_key/vsn.mk b/lib/public_key/vsn.mk
index 22956d224d..0de40d1d3f 100644
--- a/lib/public_key/vsn.mk
+++ b/lib/public_key/vsn.mk
@@ -1 +1 @@
-PUBLIC_KEY_VSN = 1.9.1
+PUBLIC_KEY_VSN = 1.9.2
diff --git a/lib/snmp/configure.in b/lib/snmp/configure.in
index f9ca8f9ac4..bac042ccca 100644
--- a/lib/snmp/configure.in
+++ b/lib/snmp/configure.in
@@ -4,12 +4,7 @@ define([AC_CACHE_SAVE], )dnl
AC_INIT(vsn.mk)
-if test -z "$ERL_TOP" || test ! -d $ERL_TOP ; then
- AC_CONFIG_AUX_DIRS(autoconf)
-else
- erl_top=${ERL_TOP}
- AC_CONFIG_AUX_DIRS($erl_top/erts/autoconf)
-fi
+AC_CONFIG_AUX_DIRS(${ERL_TOP}/erts/autoconf)
if test "X$host" != "Xfree_source" -a "X$host" != "Xwin32"; then
AC_CANONICAL_HOST
diff --git a/lib/snmp/doc/src/notes.xml b/lib/snmp/doc/src/notes.xml
index be9a0aa966..792090f3bf 100644
--- a/lib/snmp/doc/src/notes.xml
+++ b/lib/snmp/doc/src/notes.xml
@@ -34,7 +34,123 @@
</header>
- <section><title>SNMP 5.6.1</title>
+ <section><title>SNMP 5.7.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ [manager] In a function handling snmp errors, an unused
+ result (_Error) could result in matching issues and
+ therefor case clause runtime errors (crash). Note that
+ this would only happen in *very* unusual error cases.</p>
+ <p>
+ Own Id: OTP-17161</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SNMP 5.7.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ [manager] Misspelled priv protocol (atom) made it
+ impossible to update usm user 'priv_key' configuration
+ for usmAesCfb128Protocol via function calls.</p>
+ <p>
+ Own Id: OTP-17110 Aux Id: ERIERL-586 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SNMP 5.7.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed usage of <c>AC_CONFIG_AUX_DIRS()</c> macros in
+ configure script sources.</p>
+ <p>
+ Own Id: OTP-17093 Aux Id: ERL-1447, PR-2948 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SNMP 5.7</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ If an attempt was made to send a v1 trap on a IPv6
+ transport this could cause a master agent crash (if the
+ agent was *not* multi-threaded).</p>
+ <p>
+ Own Id: OTP-16920 Aux Id: OTP-16649 </p>
+ </item>
+ <item>
+ <p>
+ The deprecation info for a couple of the deprecated MIB
+ compiler functions where incorrect. Referred to functions
+ in the 'snmpa' module instead of 'snmpc'.</p>
+ <p>
+ Own Id: OTP-17056 Aux Id: OTP-17049 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Make it possible for the agent to configure separate
+ transports (sockets) for request-responder and
+ trap-sender.</p>
+ <p>
+ Own Id: OTP-16649</p>
+ </item>
+ <item>
+ <p>
+ The mib server cache handling has been improved. First,
+ the default gclimit has been changed from 100 to infinity
+ (to ensure the size is as small as possible). Also, the
+ method of removing old elements has been optimized.</p>
+ <p>
+ Own Id: OTP-16989 Aux Id: ERIERL-544 </p>
+ </item>
+ <item>
+ <p>
+ It is now possible to configure the agent in such a way
+ that the order of outgoing notifications are processed in
+ order in the agent. What happens after the notification
+ message has left the agent (been sent) is of course still
+ out of our control.</p>
+ <p>
+ Own Id: OTP-17022 Aux Id: ERIERL-492 </p>
+ </item>
+ <item>
+ <p>
+ Improve handling of the udp_error message. Basically an
+ improved error/warning message.</p>
+ <p>
+ Own Id: OTP-17033</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SNMP 5.6.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
@@ -142,6 +258,35 @@
</section>
+ <section><title>SNMP 5.5.0.4</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ The mib server cache handling has been improved. First,
+ the default gclimit has been changed from 100 to infinity
+ (in order to ensure the size is as small as possible).
+ Also the method of removing old elements has been
+ optimized.</p>
+ <p>
+ Own Id: OTP-16989 Aux Id: ERIERL-544 </p>
+ </item>
+ <item>
+ <p>
+ It is now possible to configure the agent in such a way
+ that the order of outgoing notifications are processed in
+ order in the agent. What happens after the notification
+ message has left the agent (been sent) is of course still
+ out of our control.</p>
+ <p>
+ Own Id: OTP-17022 Aux Id: ERIERL-492 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>SNMP 5.5.0.3</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/snmp/doc/src/snmp_agent_config_files.xml b/lib/snmp/doc/src/snmp_agent_config_files.xml
index 7fda4da31f..8456b84952 100644
--- a/lib/snmp/doc/src/snmp_agent_config_files.xml
+++ b/lib/snmp/doc/src/snmp_agent_config_files.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>1997</year><year>2016</year>
+ <year>1997</year><year>2020</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -100,7 +100,7 @@
<p><c>{AgentVariable, Value}.</c></p>
<list type="bulleted">
<item>
- <p><c>AgentVariable</c> is one of the variables is
+ <p><c>AgentVariable</c> is one of the variables in
SNMP-FRAMEWORK-MIB or one of the internal variables
<c>intAgentUDPPort</c>, which defines which UDP port the agent
listens to, or <c>intAgentTransports</c>, which defines the
@@ -119,29 +119,79 @@
{snmpEngineID, "mbj's engine"}.
{snmpEngineMaxPacketSize, 484}.
</pre>
- <p>The value of <c>intAgentTransports</c> is a list of
- <c>{Domain, Addr}</c> tuples, where <c>Domain</c>
- is either <c>transportDomainUdpIpv4</c> or <c>transportDomainUdpIpv6</c>,
- and <c>Addr</c> is the address in the domain.
- <c>Addr</c> can be specified either as an
- <c>IpAddr</c> or as an <c>{IpAddr, IpPort}</c> tuple.
- <c>IpAddr</c> is either a regular Erlang/OTP
- <seetype marker="kernel:inet#ip_address"><c>ip_address()</c></seetype>
- or a traditional SNMP integer list and <c>IpPort</c> is an integer.
- </p>
- <p>When the <c>Addr</c> value does not contain a port number,
+ <p>These are the supported entries and their value types: </p>
+
+ <pre>
+ {snmpEngine, string()}. % Mandatory
+ {snmpEngineMaxMessageSize, non_neg_integer()}. % Mandatory
+ {intAgentUDPPort, pos_integer()}. % Optional
+ {intAgentTransports, intAgentTransports()}. % Mandatory
+ </pre>
+
+ <p>And here are the transport value types: </p>
+
+ <pre>
+ intAgentTransports() :: [intAgentTransport()].
+ intAgentTransport() :: {TDomain, Addr} |
+ {TDomain, EAddr, Kind} |
+ {TDomain, EAddr, Opts} |
+ {TDomain, EAddr, Kind, Opts}
+ TDomain :: transportDomainUdpIpv4 | transportDomainUdpIpv6.
+ Addr :: {IpAddr, IpPort} | IpAddr.
+ IpAddr :: <seetype marker="kernel:inet#ip_address"><c>inet:ip_address()</c></seetype> | snmpIpAddr().
+ snmpIpAddr() :: [non_neg_integer()].
+ IpPort :: pos_integer().
+ EAddr :: {<seetype marker="kernel:inet#ip_address"><c>inet:ip_address()</c></seetype>, PortInfo}.
+ PortInfo :: pos_integer() | system | range() | ranges().
+ range() :: {Min :: pos_integer(), Max :: pos_integer()}, Min &lt; Max
+ ranges() :: [pos_integer() | range()].
+ Kind :: req_responder | trap_sender.
+ Opts :: list().
+ </pre>
+
+ <p>If a "traditional" transport is specified (without explicit <c>Kind</c>,
+ handling both requests and traps) for a transport domain, its <em>not</em>
+ possible to also specify a transport (for that domain) with a specific
+ <c>Kind</c>. This is for example <em>not</em> allowed:</p>
+
+ <pre>
+ [{transportDomainUdpIpv4, {{141,213,11,24}, 4000}},
+ {transportDomainUdpIpv4, {{141,213,11,24}, 4001}, trap_sender}].
+ </pre>
+
+ <p>Note that only one transport per kind for each transport domain
+ can be configured. </p>
+
+ <p><c>PortInfo</c> <c>system</c> is used to indicate that the 'system'
+ should choose (the way port number '0' (zero) is normally used).
+ Port info '0' (zero) cannot be used for this, since it is (internally) used
+ to represent the 'default' port number. </p>
+
+ <p>In the traditional transport entries, when the <c>Addr</c> value
+ does not contain a port number,
the value of <c>intAgentUDPPort</c> is used.</p>
- <p>The legacy and intermediate variables <c>intAgentIpAddress</c>
- and <c>intAgentTransportDomain</c> are still supported so old
- <c>agent.conf</c> files will work.
- </p>
+ <p>Note that the (new) extended transport entries (including <c>Kind</c>
+ and <c>Opts</c>) <em>must</em> specify port-info as they ignore
+ any value specified by <c>intAgentUDPPort</c>.</p>
+ <p><c>Opts</c> is the same as for the
+ <seeguide marker="snmp_config#agent_ni_opts">net-if</seeguide>
+ process <em>and</em>
+ takes precedence (for that transport) if present.
+ The point is that each transport can have its own socket options. </p>
+
<p>The value of <c>snmpEngineID</c> is a string, which for a
deployed agent should have a very specific structure. See
RFC 2271/2571 for details.</p>
+ <note><p>The legacy and intermediate variables <c>intAgentIpAddress</c>
+ and <c>intAgentTransportDomain</c> are still supported so old
+ <c>agent.conf</c> files will work.</p>
+ <p>But they <em>cannot</em> be combined with intAgentTransports.</p>
+ </note>
+
<marker id="context"></marker>
</section>
diff --git a/lib/snmp/doc/src/snmp_app.xml b/lib/snmp/doc/src/snmp_app.xml
index 3f26476365..361781cb61 100644
--- a/lib/snmp/doc/src/snmp_app.xml
+++ b/lib/snmp/doc/src/snmp_app.xml
@@ -188,22 +188,46 @@ in the snmp_config file!
<tag><marker id="agent_orig_disco_opts"></marker>
<c><![CDATA[agent_originating_discovery_opts() = [agent_originating_discovery_opt()] <optional>]]></c></tag>
<item>
- <p><c>agent_originating_discovery_opt() =
- {enable, boolean()}</c></p>
- <p>These are options effecting discovery <c>originating</c> in this
- agent. </p>
- <p>The default values for the <c>originating</c>
- discovery options are: </p>
- <list type="bulleted">
- <item>enable: <c>true</c></item>
+ <p><c>agent_originating_discovery_opt() = {enable, boolean()}</c></p>
+ <p>These are options effecting discovery <c>originating</c> in this
+ agent. </p>
+ <p>The default values for the <c>originating</c>
+ discovery options are: </p>
+ <list type="bulleted">
+ <item>enable: <c>true</c></item>
</list>
</item>
<tag><marker id="agent_mt"></marker>
- <c><![CDATA[multi_threaded() = bool() <optional>]]></c></tag>
+ <c><![CDATA[multi_threaded() = bool() | extended <optional>]]></c></tag>
<item>
- <p>If <c>true</c>, the agent is multi-threaded, with one
- thread for each get request. </p>
+ <p>If <c>true</c> (or <c>extended</c>), the agent is multi-threaded,
+ with one thread for each get request.</p>
+ <p>The value <c>extended</c> means that a special 'process'
+ is also created intended to handle <em>all</em> notifications. </p>
+ <list>
+ <item>
+ <p><c>true</c> - One worker dedicated to 'set-requests' and one
+ (main) worker for all other requests ('get-request' and
+ notifications).</p>
+ <p>If the 'main' worker is busy, a temporary process is
+ spawned to handle that job ('get-request' or notification). </p>
+ </item>
+ <item>
+ <p><c>extended</c> - One worker dedicated to 'set-requests',
+ one worker dedicated to notifications and one
+ (main) worker for all 'get-requests'. </p>
+ <p>If the 'main' worker is busy, a temporary process is
+ spawned to handle that 'get-request'. </p>
+ </item>
+ </list>
+ <note>
+ <p>Even with multi-threaded set to <c>extended</c>
+ there is still a risk for 'reorder' when sending inform-requsts,
+ which require a response (and may therefor require resending). </p>
+ <p>Also, there is of course no way to guarantee order once the
+ package is on the network. </p>
+ </note>
<p>Default is <c>false</c>.</p>
</item>
@@ -271,16 +295,23 @@ in the snmp_config file!
<c><![CDATA[agent_net_if_options() = [agent_net_if_option()] <optional>]]></c></tag>
<item>
<p><c>agent_net_if_option() = {bind_to, bind_to()} |
- {sndbuf, sndbuf()} |
- {recbuf, recbuf()} |
- {no_reuse, no_reuse()} |
- {req_limit, req_limit()} |
- {filter, agent_net_if_filter_options()} |
- {extra_sock_opts, extra_socket_options()}</c></p>
- <p>These options are actually specific to the used module.
- The ones shown here are applicable to the default
- <c>agent_net_if_module()</c>.</p>
- <p>For defaults see the options in <c>agent_net_if_option()</c>.</p>
+ {sndbuf, sndbuf()} |
+ {recbuf, recbuf()} |
+ {no_reuse, no_reuse()} |
+ {req_limit, req_limit()} |
+ {filter, agent_net_if_filter_options()} |
+ {extra_sock_opts, extra_socket_options()}</c></p>
+ <p>These options are actually specific to the used module.
+ The ones shown here are applicable to the default
+ <c>agent_net_if_module()</c>.</p>
+ <note>
+ <p>If the user has configured transports <em>with</em> options
+ then those will take precedence over these options.
+ See
+ <seeguide marker="snmp_agent_config_files#agent_information">agent information</seeguide>
+ for more info. </p>
+ </note>
+ <p>For defaults see the options in <c>agent_net_if_option()</c>.</p>
</item>
<tag><marker id="agent_ni_req_limit"></marker>
@@ -526,14 +557,20 @@ in the snmp_config file!
</item>
<tag><marker id="agent_ms_cache_gclimit"></marker>
- <c><![CDATA[mibs_cache_gclimit() = integer() > 0 | infinity <optional>]]></c></tag>
+ <c><![CDATA[mibs_cache_gclimit() = infinity | integer() > 0 <optional>]]></c></tag>
<item>
<p>When performing a GC, this is the max number of cache entries
that will be deleted from the cache. </p>
- <p>The reason for having this limit is that if the cache is
+
+ <p>The reason why its possible to set a limit, is that if the cache is
large, the GC can potentially take a long time, during which
- the agent is locked. </p>
- <p>Default is <c>100</c>.</p>
+ the agent is "busy".
+ <em>But</em> on a heavily loaded system, we also risk not removing
+ enough elements in the cache, instead causing it to grow over time.
+ This is the reason the default value is <c>infinity</c>, which will
+ ensure that <em>all</em> candidates are removed as soon as possible. </p>
+
+ <p>Default is <c>infinity</c>.</p>
</item>
<tag><marker id="agent_error_report_mod"></marker>
diff --git a/lib/snmp/doc/src/snmp_config.xml b/lib/snmp/doc/src/snmp_config.xml
index 58933ebb2b..e3d74fec16 100644
--- a/lib/snmp/doc/src/snmp_config.xml
+++ b/lib/snmp/doc/src/snmp_config.xml
@@ -184,10 +184,35 @@
</item>
<tag><marker id="agent_mt"></marker>
- <c><![CDATA[multi_threaded() = bool() <optional>]]></c></tag>
+ <c><![CDATA[multi_threaded() = bool() | extended<optional>]]></c></tag>
<item>
- <p>If <c>true</c>, the agent is multi-threaded, with one
- thread for each get request. </p>
+ <p>If <c>true</c> (or <c>extended</c>), the agent is multi-threaded,
+ with one thread for each get request. </p>
+ <p>The value <c>extended</c> means that a special 'process'
+ is also created intended to handle <em>all</em> notifications. </p>
+ <list>
+ <item>
+ <p><c>true</c> - One worker dedicated to 'set-requests' and one
+ (main) worker for all other requests ('get-request' and
+ notifications).</p>
+ <p>If the 'main' worker is busy, a temporary process is
+ spawned to handle that job ('get-request' or notification). </p>
+ </item>
+ <item>
+ <p><c>extended</c> - One worker dedicated to 'set-requests',
+ one worker dedicated to notifications and one
+ (main) worker for all 'get-requests'. </p>
+ <p>If the 'main' worker is busy, a temporary process is
+ spawned to handle that 'get-request'. </p>
+ </item>
+ </list>
+ <note>
+ <p>Even with multi-threaded set to <c>extended</c>
+ there is still a risk for 'reorder' when sending inform-requsts,
+ which require a response (and may therefor require resending). </p>
+ <p>Also, there is of course no way to guarantee order once the
+ package is on the network. </p>
+ </note>
<p>Default is <c>false</c>.</p>
</item>
@@ -256,15 +281,22 @@
<c><![CDATA[agent_net_if_options() = [agent_net_if_option()] <optional>]]></c></tag>
<item>
<p><c>agent_net_if_option() = {bind_to, bind_to()} |
- {sndbuf, sndbuf()} |
- {recbuf, recbuf()} |
- {no_reuse, no_reuse()} |
- {req_limit, req_limit()} |
- {filter, agent_net_if_filter_options()} |
- {extra_sock_opts, extra_socket_options()}</c></p>
+ {sndbuf, sndbuf()} |
+ {recbuf, recbuf()} |
+ {no_reuse, no_reuse()} |
+ {req_limit, req_limit()} |
+ {filter, agent_net_if_filter_options()} |
+ {extra_sock_opts, extra_socket_options()}</c></p>
<p>These options are actually specific to the used module.
- The ones shown here are applicable to the default
- <c>agent_net_if_module()</c>.</p>
+ The ones shown here are applicable to the default
+ <c>agent_net_if_module()</c>.</p>
+ <note>
+ <p>If the user has configured transports <em>with</em> options
+ then those will take precedence over these options.
+ See
+ <seeguide marker="snmp_agent_config_files#agent_information">agent information</seeguide>
+ for more info. </p>
+ </note>
<p>For defaults see the options in <c>agent_net_if_option()</c>.</p>
</item>
@@ -544,14 +576,19 @@ in so far as it will be converted to the new format if found.
</item>
<tag><marker id="agent_ms_cache_gclimit"></marker>
- <c><![CDATA[mibs_cache_gclimit() = integer() > 0 | infinity <optional>]]></c></tag>
+ <c><![CDATA[mibs_cache_gclimit() = infinity | integer() > 0 <optional>]]></c></tag>
<item>
<p>When performing a GC, this is the max number of cache entries
that will be deleted from the cache. </p>
- <p>The reason for having this limit is that if the cache is
+
+ <p>The reason why its possible to set a limit, is that if the cache is
large, the GC can potentially take a long time, during which
- the agent is locked. </p>
- <p>Default is <c>100</c>.</p>
+ the agent is "busy".
+ <em>But</em> on a heavily loaded system, we also risk not removing
+ enough elements in the cache, instead causing it to grow over time.
+ This is the reason the default value is <c>infinity</c>, which will
+ ensure that <em>all</em> candidates are removed as soon as possible. </p>
+ <p>Default is <c>infinity</c>.</p>
</item>
<tag><marker id="agent_error_report_mod"></marker>
diff --git a/lib/snmp/src/agent/snmp_framework_mib.erl b/lib/snmp/src/agent/snmp_framework_mib.erl
index 6db6f87a85..5db686d29b 100644
--- a/lib/snmp/src/agent/snmp_framework_mib.erl
+++ b/lib/snmp/src/agent/snmp_framework_mib.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2019. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -52,6 +52,7 @@
get_engine_boots/0, get_engine_time/0,
set_engine_boots/1, set_engine_time/1,
table_next/2, check_status/3]).
+-export([which_trap_transport/1, which_req_transport/1, which_transport/2]).
-export([add_context/1, delete_context/1]).
-export([check_agent/2, check_context/1, order_agent/2]).
@@ -191,49 +192,198 @@ check_context(Context) ->
%% {Name, Value}.
%%-----------------------------------------------------------------
check_agent(Entry, undefined) ->
- check_agent(Entry, {snmp_target_mib:default_domain(), undefined});
-check_agent({intAgentTransportDomain, Domain}, {_, Port}) ->
- {snmp_conf:check_domain(Domain), {Domain, Port}};
-check_agent({intAgentUDPPort, Port}, {Domain, _}) ->
+ check_agent(Entry, #{domain => snmp_target_mib:default_domain(),
+ port => undefined});
+
+%% <BACKWARD-COMPAT>
+check_agent({intAgentTransportDomain, Domain},
+ #{transports := T} = State) when is_list(T) andalso (T =/= []) ->
+ ?vinfo("check_agent(intAgentTransportDomain) -> entry with"
+ "~n Domain: ~p"
+ "~n when"
+ "~n State: ~p", [Domain, State]),
+ error({transports_already_defined, T});
+check_agent({intAgentTransportDomain, Domain}, State) ->
+ ?vtrace("check_agent(intAgentTransportDomain) -> entry with"
+ "~n Domain: ~p", [Domain]),
+ {snmp_conf:check_domain(Domain), State#{domain => Domain}};
+%% </BACKWARD-COMPAT>
+
+check_agent({intAgentUDPPort, NewPort}, #{port := OldPort})
+ when is_integer(OldPort) ->
+ ?vinfo("check_agent(intAgentUDPPort) -> entry with"
+ "~n New Port: ~p"
+ "~n when"
+ "~n Old Port: ~p", [NewPort, OldPort]),
+ error({port_already_defined, OldPort});
+check_agent({intAgentUDPPort, Port}, State) ->
+ ?vtrace("check_agent(intAgentUDPPort) -> entry with"
+ "~n Port: ~p"
+ "~n when"
+ "~n State: ~p", [Port, State]),
ok = snmp_conf:check_port(Port),
- {ok, {Domain, Port}};
-check_agent({intAgentIpAddress, _}, {_, undefined}) ->
+ {ok, State#{port => Port}};
+
+%% <BACKWARD-COMPAT>
+check_agent({intAgentIpAddress, Ip}, #{port := undefined}) ->
+ ?vinfo("check_agent(intAgentIpAddress) -> "
+ "entry when port not defined with"
+ "~n Ip: ~p", [Ip]),
error({missing_mandatory, intAgentUDPPort});
-check_agent({intAgentIpAddress = Tag, Ip} = Entry, {Domain, Port} = State) ->
- {case snmp_conf:check_ip(Domain, Ip) of
- ok ->
- [Entry,
- {intAgentTransports, [{Domain, {Ip, Port}}]}];
- {ok, FixedIp} ->
- [{Tag, FixedIp},
- {intAgentTransports, [{Domain, {FixedIp, Port}}]}]
- end, State};
-check_agent({intAgentTransports = Tag, Transports}, {_, Port} = State)
+check_agent({intAgentIpAddress = _Tag, Ip} = _Entry,
+ #{transports := T} = _State) when (T =/= []) ->
+ ?vinfo("check_agent(intAgentIpAddress) -> "
+ "entry when transports already defined with"
+ "~n Ip: ~p"
+ "~n when"
+ "~n Transports: ~p", [Ip, T]),
+ error({transports_already_defined, T});
+check_agent({intAgentIpAddress = Tag, Ip} = _Entry,
+ #{domain := Domain, port := Port} = State) ->
+ ?vtrace("check_agent(intAgentIpAddress) -> entry with"
+ "~n Ip: ~p"
+ "~n when"
+ "~n Domain: ~p"
+ "~n Port: ~p", [Ip, Domain, Port]),
+ FixedIp = case snmp_conf:check_ip(Domain, Ip) of
+ ok ->
+ Ip;
+ {ok, FIp} ->
+ FIp
+ end,
+ T = [{Domain, {FixedIp, Port}, all, []}],
+ Rows = [{Tag, FixedIp}, {intAgentTransports, T}],
+ {Rows, State#{transports => T}};
+%% </BACKWARD-COMPAT>
+
+check_agent({intAgentTransports = _Tag, _Transports},
+ #{transports := T} = _State) when (T =/= []) ->
+ ?vinfo("check_agent(intAgentTransports) -> "
+ "entry when transports already defined with"
+ "~n T: ~p", [T]),
+ error({transports_already_defined, T});
+check_agent({intAgentTransports = Tag, Transports}, #{port := Port} = State)
when is_list(Transports) ->
+ ?vtrace("check_agent(intAgentTransports) -> entry when"
+ "~n Port: ~p", [Port]),
+ CheckAddress =
+ fun(D, A, undefined) ->
+ snmp_conf:check_address(D, A);
+ (D, A, P) ->
+ snmp_conf:check_address(D, A, P)
+ end,
CheckedTransports =
[case Transport of
{Domain, Address} ->
- case
- case Port of
- undefined ->
- snmp_conf:check_address(Domain, Address);
- _ ->
- snmp_conf:check_address(Domain, Address, Port)
- end
- of
- ok ->
- Transport;
- {ok, FixedAddress} ->
- {Domain, FixedAddress}
- end;
+ ?vtrace("check_agent(intAgentTransports) -> check transport: "
+ "~n Domain: ~p"
+ "~n Address: ~p", [Domain, Address]),
+ CheckedAddress =
+ case CheckAddress(Domain, Address, Port) of
+ ok ->
+ Address;
+ {ok, Address2} ->
+ Address2
+ end,
+ ?vtrace("check_agent(intAgentTransports) -> checked address: "
+ "~n ~p", [CheckedAddress]),
+ {Domain, CheckedAddress, all, []};
+
+ {Domain, Address, Kind} when is_atom(Kind) ->
+ ?vtrace("check_agent(intAgentTransports) -> check transport: "
+ "~n Domain: ~p"
+ "~n Address: ~p"
+ "~n Kind: ~p", [Domain, Address, Kind]),
+ ok = snmp_conf:check_transport_kind(Kind),
+ ?vtrace("check_agent(intAgentTransports) -> checked kind"),
+ case snmp_conf:check_transport_address(Domain, Address) of
+ true ->
+ ?vtrace("check_agent(intAgentTransports) -> "
+ "checked transport address"),
+ {Domain, Address, Kind, []};
+ false ->
+ ?vinfo("check_agent(intAgentTransports) -> "
+ "invalid transport address: "
+ "~n ~p", [Address]),
+ error({bad_transport_addr, Address})
+ end;
+
+ {Domain, Address, Opts} when is_list(Opts) ->
+ ?vtrace("check_agent(intAgentTransports) -> check transport: "
+ "~n Domain: ~p"
+ "~n Address: ~p"
+ "~n Opts: ~p", [Domain, Address, Opts]),
+ CheckedOpts = snmp_conf:check_transport_opts(Opts),
+ ?vtrace("check_agent(intAgentTransports) -> checked opts: "
+ "~n ~p", [CheckedOpts]),
+ case snmp_conf:check_transport_address(Domain, Address) of
+ true ->
+ ?vtrace("check_agent(intAgentTransports) -> "
+ "checked transport address"),
+ {Domain, Address, all, CheckedOpts};
+ false ->
+ ?vinfo("check_agent(intAgentTransports) -> "
+ "invalid transport address: "
+ "~n ~p", [Address]),
+ error({bad_transport_addr, Address})
+ end;
+
+ {Domain, Address, Kind, Opts} ->
+ ?vtrace("check_agent(intAgentTransports) -> check transport: "
+ "~n Domain: ~p"
+ "~n Address: ~p"
+ "~n Kind: ~p"
+ "~n Opts: ~p", [Domain, Address, Kind, Opts]),
+ ok = snmp_conf:check_transport_kind(Kind),
+ ?vtrace("check_agent(intAgentTransports) -> checked kind"),
+ CheckedOpts = snmp_conf:check_transport_opts(Opts),
+ ?vtrace("check_agent(intAgentTransports) -> checked opts: "
+ "~n ~p", [CheckedOpts]),
+ case snmp_conf:check_transport_address(Domain, Address) of
+ true ->
+ ?vtrace("check_agent(intAgentTransports) -> "
+ "checked transport address"),
+ {Domain, Address, Kind, CheckedOpts};
+ false ->
+ ?vinfo("check_agent(intAgentTransports) -> "
+ "invalid transport address: "
+ "~n ~p", [Address]),
+ error({bad_transport_addr, Address})
+ end;
+
_ ->
- error({bad_transport, Transport})
+ ?vinfo("check_agent(intAgentTransports) -> invalid transport:"
+ "~n ~p", [Transport]),
+ error({bad_transport, Transport})
end
|| Transport <- Transports],
+ validate_transports(CheckedTransports),
+ ?vtrace("check_agent(intAgentTransports) -> checked transports"),
{{ok, {Tag, CheckedTransports}}, State};
check_agent(Entry, State) ->
+ ?vtrace("check_agent -> entry when"
+ "~n Entry: ~p", [Entry]),
{check_agent(Entry), State}.
+%% Basically this is intended to check that there are no
+%% inconsistencies (between transports). Such as specifying
+%% both an old style transport (Kind = all) and transports
+%% with specified Kind:s (rep_responser or trap_sender).
+validate_transports(Transports) ->
+ validate_transports(Transports, false).
+
+validate_transports([] = _Transports, _All) ->
+ ok;
+validate_transports([{_Domain, _Addr, all, _Opts} | Transports], _All) ->
+ validate_transports(Transports, true);
+validate_transports([{_Domain, _Addr, Kind, _Opts} | _Transports], true)
+ when (Kind =/= all) ->
+ error({bad_transport_kind, Kind});
+validate_transports([{_Domain, _Addr, _Kind, _Opts} | Transports], All) ->
+ validate_transports(Transports, All).
+
+
+
%% This one is kept for backwards compatibility
check_agent({intAgentMaxPacketSize, Value}) ->
snmp_conf:check_packet_size(Value);
@@ -246,6 +396,7 @@ check_agent(X) ->
%% Ordering function to sort intAgentTransportDomain first
%% hence before intAgentIpAddress. Sort other entries on the key.
+%% Note that neither of these are required!
-dialyzer({nowarn_function, order_agent/2}).
order_agent(EntryA, EntryB) ->
snmp_conf:keyorder(
@@ -268,7 +419,7 @@ init_vars(Vars) ->
init_var({Var, Val}) ->
?vtrace("init var: "
- "~n set ~w to ~w",[Var, Val]),
+ "~n set ~w to ~w", [Var, Val]),
snmp_generic:variable_set(db(Var), Val).
init_tabs(Contexts) ->
@@ -432,6 +583,41 @@ intAgentTransports(Op) ->
snmp_generic:variable_func(Op, db(intAgentTransports)).
+which_trap_transport(Domain) when (Domain =:= snmpUDPDomain) ->
+ case which_transport(Domain, all) of
+ {value, _} = VALUE ->
+ VALUE;
+ false ->
+ which_transport(transportDomainUdpIpv4, all)
+ end;
+which_trap_transport(Domain) ->
+ case which_transport(Domain, trap_sender) of
+ {value, _} = VALUE ->
+ VALUE;
+ false ->
+ which_transport(Domain, all)
+ end.
+
+which_req_transport(Domain) ->
+ which_transport(Domain, req_responder).
+
+which_transport(Domain, Kind) ->
+ {value, Transports} = intAgentTransports(get),
+ which_transport(Domain, Kind, Transports).
+
+which_transport(_Domain, _Kind, []) ->
+ false;
+which_transport(Domain, Kind,
+ [{Domain, _Addr, _Kind, _Opts} = Transport|_Transports])
+ when (Kind =:= all) ->
+ {value, Transport};
+which_transport(Domain, Kind,
+ [{Domain, _Addr, Kind, _Opts} = Transport|_Transports]) ->
+ {value, Transport};
+which_transport(Domain, Kind, [_Transport|Transports]) ->
+ which_transport(Domain, Kind, Transports).
+
+
snmpEngineID(print) ->
VarAndValue = [{snmpEngineID, snmpEngineID(get)}],
diff --git a/lib/snmp/src/agent/snmpa_agent.erl b/lib/snmp/src/agent/snmpa_agent.erl
index cc3bef15af..5039b08391 100644
--- a/lib/snmp/src/agent/snmpa_agent.erl
+++ b/lib/snmp/src/agent/snmpa_agent.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -63,7 +63,7 @@
-export([get_request_limit/1, set_request_limit/2]).
-export([invalidate_ca_cache/0]).
-export([increment_counter/3]).
--export([restart_worker/1, restart_set_worker/1]).
+-export([restart_worker/1, restart_set_worker/1, restart_notif_worker/1]).
%% For backward compatibillity
-export([send_trap/6, send_trap/7]).
@@ -71,7 +71,7 @@
%% Internal exports
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3, tr_var/2, tr_varbind/1,
- handle_pdu/8, worker/2, worker_loop/1,
+ handle_pdu/8, worker/4, worker_loop/2,
do_send_trap/7, do_send_trap/8]).
%% <BACKWARD-COMPAT>
-export([handle_pdu/7,
@@ -177,6 +177,7 @@
worker,
worker_state = ready,
set_worker,
+ notif_worker,
multi_threaded,
ref,
vsns,
@@ -249,6 +250,9 @@ restart_worker(Agent) ->
restart_set_worker(Agent) ->
call(Agent, restart_set_worker).
+restart_notif_worker(Agent) ->
+ call(Agent, restart_notif_worker).
+
get_log_type(Agent) ->
call(Agent, get_log_type).
@@ -391,11 +395,12 @@ do_init(Prio, Parent, Ref, Options) ->
put(net_if, NetIfPid),
put(mibserver, MibPid),
process_flag(trap_exit, true),
- {Worker, SetWorker} = workers_start(MultiT),
+ {Worker, SetWorker, NotifWorker} = workers_start(MultiT),
{ok, #state{type = Type,
parent = Parent,
worker = Worker,
set_worker = SetWorker,
+ notif_worker = NotifWorker,
multi_threaded = MultiT,
ref = Ref,
vsns = Vsns,
@@ -975,9 +980,13 @@ handle_info({'EXIT', Pid, Reason}, #state{worker = Pid} = S) ->
NewWorker = worker_start(),
{noreply, S#state{worker = NewWorker}};
handle_info({'EXIT', Pid, Reason}, #state{set_worker = Pid} = S) ->
- ?vlog("set-worker (~p) exited -> create new ~n ~p", [Pid,Reason]),
+ ?vlog("set-worker (~p) exited -> create new ~n ~p", [Pid, Reason]),
NewWorker = set_worker_start(),
{noreply, S#state{set_worker = NewWorker}};
+handle_info({'EXIT', Pid, Reason}, #state{notif_worker = Pid} = S) ->
+ ?vlog("notif-worker (~p) exited -> create new ~n ~p", [Pid, Reason]),
+ NewWorker = notif_worker_start(),
+ {noreply, S#state{notif_worker = NewWorker}};
handle_info({'EXIT', Pid, Reason}, #state{parent = Pid} = S) ->
?vlog("parent (~p) exited for reason ~n~p", [Pid,Reason]),
{stop, {parent_died, Reason}, S};
@@ -1415,13 +1424,17 @@ handle_cast({verbosity, Verbosity}, S) ->
?vlog("verbosity: ~p -> ~p",[get(verbosity), Verbosity]),
put(verbosity,snmp_verbosity:validate(Verbosity)),
case S#state.worker of
- Pid when is_pid(Pid) -> Pid ! ?mk_verbosity_wreq(Verbosity);
+ Pid1 when is_pid(Pid1) -> Pid1 ! ?mk_verbosity_wreq(Verbosity);
_ -> ok
end,
case S#state.set_worker of
Pid2 when is_pid(Pid2) -> Pid2 ! ?mk_verbosity_wreq(Verbosity);
_ -> ok
end,
+ case S#state.notif_worker of
+ Pid3 when is_pid(Pid3) -> Pid3 ! ?mk_verbosity_wreq(Verbosity);
+ _ -> ok
+ end,
{noreply, S};
handle_cast({sub_agents_verbosity,Verbosity}, S) ->
@@ -1447,14 +1460,16 @@ handle_cast(Msg, S) ->
{noreply, S}.
-terminate(shutdown, #state{worker = Worker,
- set_worker = SetWorker,
- backup = Backup,
+terminate(shutdown, #state{worker = Worker,
+ set_worker = SetWorker,
+ notif_worker = NotifWorker,
+ backup = Backup,
ref = Ref}) ->
%% Ordered shutdown - stop misc-workers, net_if, mib-server and note-store.
backup_server_stop(Backup),
worker_stop(Worker, 100),
worker_stop(SetWorker, 100),
+ worker_stop(NotifWorker, 100),
snmpa_misc_sup:stop_net_if(Ref),
snmpa_misc_sup:stop_mib_server(Ref);
terminate(_Reason, _S) ->
@@ -1600,20 +1615,26 @@ backup_server_stop(_) ->
ok.
+workers_start(extended) ->
+ ?vdebug("start worker, set-worker and notif-worker", []),
+ {worker_start(), set_worker_start(), notif_worker_start()};
workers_start(true) ->
- ?vdebug("start worker and set-worker",[]),
- {worker_start(), set_worker_start()};
+ ?vdebug("start worker and set-worker", []),
+ {worker_start(), set_worker_start(), undefined};
workers_start(_) ->
- {undefined, undefined}.
+ {undefined, undefined, undefined}.
worker_start() ->
- worker_start(get()).
+ worker_start(mw, true, get()).
set_worker_start() ->
- worker_start([{master, self()} | get()]).
+ worker_start(sw, false, [{master, self()} | get()]).
+
+notif_worker_start() ->
+ worker_start(nw, false, [{master, self()} | get()]).
-worker_start(Dict) ->
- proc_lib:spawn_link(?MODULE, worker, [self(), Dict]).
+worker_start(SName, Report, Dict) ->
+ proc_lib:spawn_link(?MODULE, worker, [self(), SName, Report, Dict]).
%% worker_stop(Pid) ->
%% worker_stop(Pid, infinity).
@@ -1783,13 +1804,13 @@ do_send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs,
snmpa_trap:send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs,
LocalEngineID, ExtraInfo, get(net_if)).
-worker(Master, Dict) ->
+worker(Master, SName, Report, Dict) ->
lists:foreach(fun({Key, Val}) -> put(Key, Val) end, Dict),
- put(sname, worker_short_name(get(sname))),
+ put(sname, worker_short_name(get(sname), SName)),
?vlog("starting",[]),
- worker_loop(Master).
+ worker_loop(Master, Report).
-worker_loop(Master) ->
+worker_loop(Master, Report) ->
Res =
receive
#wrequest{cmd = handle_pdu,
@@ -1813,7 +1834,7 @@ worker_loop(Master) ->
C:E:S ->
exit({worker_crash, Req, C, E, S})
end,
- Master ! worker_available,
+ worker_maybe_announce_available(Master, Report),
HandlePduRes; % For debugging...
@@ -1840,7 +1861,7 @@ worker_loop(Master) ->
C:E:S ->
exit({worker_crash, Req, C, E, S})
end,
- Master ! worker_available,
+ worker_maybe_announce_available(Master, Report),
SendTrapRes; % For debugging...
@@ -1853,8 +1874,10 @@ worker_loop(Master) ->
#wrequest{cmd = terminate} ->
?vtrace("worker_loop -> received terminate request", []),
exit(normal);
-
-
+
+
+
+
%% *************************************************************
%%
%% Kept for backward compatibillity reasons
@@ -1904,7 +1927,12 @@ worker_loop(Master) ->
end,
?vtrace("worker_loop -> wrap with"
"~n ~p", [Res]),
- ?MODULE:worker_loop(Master).
+ ?MODULE:worker_loop(Master, Report).
+
+worker_maybe_announce_available(Master, true) ->
+ Master ! worker_available;
+worker_maybe_announce_available(_Master, _Report) ->
+ ok.
%%-----------------------------------------------------------------
@@ -2186,6 +2214,13 @@ do_handle_send_trap(S, TrapRec, NotifyName, ContextName, Recv, Varbinds,
Recv, Vbs, LocalEngineID, ExtraInfo,
get(net_if)),
{ok, S};
+ master_agent when (S#state.multi_threaded =:= extended) ->
+ %% Send to main worker
+ ?vtrace("do_handle_send_trap -> send to notif-worker",[]),
+ S#state.notif_worker ! ?mk_send_trap_wreq(TrapRec, NotifyName,
+ ContextName, Recv, Vbs,
+ LocalEngineID, ExtraInfo),
+ {ok, S};
master_agent when S#state.worker_state =:= busy ->
%% Main worker busy => create new worker
?vtrace("do_handle_send_trap -> main worker busy: "
@@ -2195,7 +2230,7 @@ do_handle_send_trap(S, TrapRec, NotifyName, ContextName, Recv, Varbinds,
{ok, S};
master_agent ->
%% Send to main worker
- ?vtrace("do_handle_send_trap -> send to main worker",[]),
+ ?vtrace("do_handle_send_trap -> send to main worker", []),
S#state.worker ! ?mk_send_trap_wreq(TrapRec, NotifyName,
ContextName, Recv, Vbs,
LocalEngineID, ExtraInfo),
@@ -3225,8 +3260,10 @@ mapfoldl(_F, _Eas, Accu, []) -> {Accu,[]}.
short_name(none) -> ma;
short_name(_Pid) -> sa.
-worker_short_name(ma) -> maw;
-worker_short_name(_) -> saw.
+worker_short_name(ma, mw) -> mamw;
+worker_short_name(ma, sw) -> masw;
+worker_short_name(ma, nw) -> manw;
+worker_short_name(_, _) -> saw.
trap_sender_short_name(ma) -> mats;
trap_sender_short_name(_) -> sats.
@@ -3318,27 +3355,33 @@ handle_set_request_limit(_, _) ->
{error, not_supported}.
-agent_info(#state{worker = W, set_worker = SW}) ->
- case (catch get_agent_info(W, SW)) of
+agent_info(#state{worker = W, set_worker = SW, notif_worker = NW}) ->
+ case (catch get_agent_info(W, SW, NW)) of
Info when is_list(Info) ->
Info;
E ->
[{error, E}]
end.
-get_agent_info(W, SW) ->
+get_agent_info(W, SW, NW) ->
MASz = proc_mem(self()),
- WSz = proc_mem(W),
- SWSz = proc_mem(SW),
ATSz = tab_mem(snmp_agent_table),
CCSz = tab_mem(snmp_community_cache),
VacmSz = tab_mem(snmpa_vacm),
- [{process_memory, [{master_agent, MASz},
- {worker, WSz},
- {set_worker, SWSz}]},
- {db_memory, [{agent, ATSz},
- {community_cache, CCSz},
- {vacm, VacmSz}]}].
+ [{process_memory,
+ [{master_agent, MASz}] ++
+ process_memory(worker, W) ++
+ process_memory(set_worker, SW) ++
+ process_memory(notif_worker, NW)},
+ {db_memory,
+ [{agent, ATSz},
+ {community_cache, CCSz},
+ {vacm, VacmSz}]}].
+
+process_memory(Tag, P) when is_pid(P) ->
+ [{Tag, proc_mem(P)}];
+process_memory(_, _) ->
+ [].
proc_mem(P) when is_pid(P) ->
case (catch erlang:process_info(P, memory)) of
diff --git a/lib/snmp/src/agent/snmpa_mib.erl b/lib/snmp/src/agent/snmpa_mib.erl
index ab1098514c..8e594213f9 100644
--- a/lib/snmp/src/agent/snmpa_mib.erl
+++ b/lib/snmp/src/agent/snmpa_mib.erl
@@ -40,14 +40,18 @@
which_cache_size/1
]).
-%% <BACKWARD-COMPAT>
--export([load_mibs/2, unload_mibs/2]).
-%% </BACKWARD-COMPAT>
+%% Utility exports
+-export([subscribe_gc_events/1, unsubscribe_gc_events/1]).
%% Internal exports
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
code_change/3]).
+%% <BACKWARD-COMPAT>
+-export([load_mibs/2, unload_mibs/2]).
+%% </BACKWARD-COMPAT>
+
+
-include_lib("kernel/include/file.hrl").
-include("snmpa_internal.hrl").
-include("snmp_types.hrl").
@@ -55,14 +59,15 @@
-include("snmp_debug.hrl").
--define(SERVER, ?MODULE).
--define(NO_CACHE, no_mibs_cache).
--define(DEFAULT_CACHE_USAGE, true).
--define(CACHE_GC_TICKTIME, timer:minutes(1)).
--define(DEFAULT_CACHE_AUTOGC, true).
--define(DEFAULT_CACHE_GCLIMIT, 100).
--define(DEFAULT_CACHE_AGE, timer:minutes(10)).
--define(CACHE_GC_TRIGGER, cache_gc_trigger).
+-define(SERVER, ?MODULE).
+-define(NO_CACHE, no_mibs_cache).
+-define(DEFAULT_CACHE_USAGE, true).
+-define(CACHE_GC_TICKTIME, timer:minutes(1)).
+-define(DEFAULT_CACHE_AUTOGC, true).
+-define(DEFAULT_CACHE_GCLIMIT, infinity). % 100).
+-define(DEFAULT_CACHE_GCVERBOSE, false).
+-define(DEFAULT_CACHE_AGE, timer:minutes(10)).
+-define(CACHE_GC_TRIGGER, cache_gc_trigger).
@@ -85,7 +90,8 @@
%%-----------------------------------------------------------------
-record(state,
{data, meo, teo, backup,
- cache, cache_tmr, cache_autogc, cache_gclimit, cache_age,
+ cache, cache_tmr, cache_autogc, cache_gclimit, cache_age,
+ cache_sub, cache_gcverbose = false,
data_mod}).
@@ -153,6 +159,13 @@ update_cache_opts(MibServer, Key, Value) ->
call(MibServer, {update_cache_opts, Key, Value}).
+subscribe_gc_events(MibServer) ->
+ call(MibServer, {subscribe_gc_events, self()}).
+
+unsubscribe_gc_events(MibServer) ->
+ call(MibServer, {unsubscribe_gc_events, self()}).
+
+
%%-----------------------------------------------------------------
%% Func: lookup/2
%% Purpose: Finds the mib entry corresponding to the Oid. If it is a
@@ -277,7 +290,7 @@ do_init(Prio, Mibs, Opts) ->
process_flag(trap_exit, true),
put(sname, ms),
put(verbosity, ?vvalidate(get_verbosity(Opts))),
- ?vlog("starting",[]),
+ ?vlog("starting", []),
%% Extract the cache options
{Cache, CacheOptions} =
@@ -291,9 +304,10 @@ do_init(Prio, Mibs, Opts) ->
Bad ->
throw({error, {bad_option, {cache, Bad}}})
end,
- CacheAutoGC = get_cacheopt_autogc(Cache, CacheOptions),
- CacheGcLimit = get_cacheopt_gclimit(Cache, CacheOptions),
- CacheAge = get_cacheopt_age(Cache, CacheOptions),
+ CacheAutoGC = get_cacheopt_autogc(Cache, CacheOptions),
+ CacheGcLimit = get_cacheopt_gclimit(Cache, CacheOptions),
+ CacheAge = get_cacheopt_age(Cache, CacheOptions),
+ CacheGcVerb = get_cacheopt_gcverbose(Cache, CacheOptions),
%% Maybe start the cache gc timer
CacheGcTimer =
@@ -322,15 +336,16 @@ do_init(Prio, Mibs, Opts) ->
?vdebug("started",[]),
MibDataMod:sync(Data2),
?vdebug("mib data synced",[]),
- {ok, #state{data = Data2,
- teo = TeOverride,
- meo = MeOverride,
- cache = Cache,
- cache_tmr = CacheGcTimer,
- cache_autogc = CacheAutoGC,
- cache_gclimit = CacheGcLimit,
- cache_age = CacheAge,
- data_mod = MibDataMod}};
+ {ok, #state{data = Data2,
+ teo = TeOverride,
+ meo = MeOverride,
+ cache = Cache,
+ cache_tmr = CacheGcTimer,
+ cache_autogc = CacheAutoGC,
+ cache_gclimit = CacheGcLimit,
+ cache_age = CacheAge,
+ cache_gcverbose = CacheGcVerb,
+ data_mod = MibDataMod}};
{'aborted at', Mib, _NewData, Reason} ->
?vinfo("failed loading mib ~p: ~p",[Mib,Reason]),
{error, {Mib, Reason}}
@@ -418,9 +433,43 @@ handle_call({update_cache_opts, Key, Value}, _From, State) ->
{Result, NewState} = handle_update_cache_opts(Key, Value, State),
{reply, Result, NewState};
+
+handle_call({subscribe_gc_events, Pid}, _From,
+ #state{cache_sub = Sub} = State)
+ when (Sub =:= undefined) ->
+ ?vdebug("subscribe_gc_events: ~p => ok", [Pid]),
+ {reply, ok, State#state{cache_sub = Pid}};
+handle_call({subscribe_gc_events, Pid}, _From,
+ #state{cache_sub = Pid} = State) ->
+ ?vinfo("subscribe_gc_events: ~p => error:already-subscribed", [Pid]),
+ {reply, {error, already_subscribed}, State};
+handle_call({subscribe_gc_events, Pid}, _From,
+ #state{cache_sub = Sub} = State)
+ when is_pid(Sub) andalso (Pid =/= Sub) ->
+ ?vinfo("subscribe_gc_events: ~p => error:already-subscribed ~p",
+ [Pid, Sub]),
+ {reply, {error, {already_subscribed, Sub}}, State};
+
+handle_call({unsubscribe_gc_events, Pid}, _From,
+ #state{cache_sub = Pid} = State) ->
+ ?vdebug("unsubscribe_gc_events: ~p => ok", [Pid]),
+ {reply, ok, State#state{cache_sub = undefined}};
+handle_call({unsubscribe_gc_events, Pid}, _From,
+ #state{cache_sub = Sub} = State)
+ when (Sub =:= undefined) ->
+ ?vinfo("unsubscribe_gc_events: ~p => error:not-subscribed", [Pid]),
+ {reply, {error, not_subscribed}, State};
+handle_call({unsubscribe_gc_events, Pid}, _From,
+ #state{cache_sub = Sub} = State)
+ when is_pid(Sub) andalso (Pid =/= Sub) ->
+ ?vinfo("unsubscribe_gc_events: ~p => error:not-subscribed ~p",
+ [Pid, Sub]),
+ {reply, {error, {not_subscribed, Sub}}, State};
+
+
handle_call({lookup, Oid}, _From,
#state{data = Data, cache = Cache, data_mod = Mod} = State) ->
- ?vlog("lookup ~p", [Oid]),
+ ?vlog("lookup ~p", [Oid]),
Key = {lookup, Oid},
{Reply, NewState} =
case maybe_cache_lookup(Cache, Key) of
@@ -431,7 +480,7 @@ handle_call({lookup, Oid}, _From,
ets:insert(Cache, {Key, Rep, timestamp()}),
{Rep, maybe_start_cache_gc_timer(State)};
[{Key, Rep, _}] ->
- ?vdebug("lookup -> found in cache", []),
+ ?vtrace("lookup -> found in cache - update timestamp", []),
ets:update_element(Cache, Key, {3, timestamp()}),
{Rep, State}
end,
@@ -458,7 +507,7 @@ handle_call({next, Oid, MibView}, _From,
ets:insert(Cache, {Key, Rep, timestamp()}),
{Rep, maybe_start_cache_gc_timer(State)};
[{Key, Rep, _}] ->
- ?vdebug("lookup -> found in cache", []),
+ ?vdebug("lookup -> found in cache - update timestamp", []),
ets:update_element(Cache, Key, {3, timestamp()}),
{Rep, State}
end,
@@ -570,7 +619,7 @@ handle_call(info, _From, #state{data = Data,
Reply =
case (catch Mod:info(Data)) of
Info when is_list(Info) ->
- [{cache, size_cache(Cache)} | Info];
+ [{cache, cache_info(Cache)} | Info];
E ->
[{error, E}]
end,
@@ -664,13 +713,21 @@ handle_info({backup_done, Reply}, #state{backup = {_, From}} = S) ->
gen_server:reply(From, Reply),
{noreply, S#state{backup = undefined}};
-handle_info(?CACHE_GC_TRIGGER, #state{cache = Cache,
- cache_age = Age,
- cache_gclimit = GcLimit,
- cache_autogc = true} = S)
+handle_info(?CACHE_GC_TRIGGER, #state{cache = Cache,
+ cache_age = Age,
+ cache_gclimit = GcLimit,
+ cache_autogc = true,
+ cache_gcverbose = GcVerbose} = S)
when (Cache =/= ?NO_CACHE) ->
- ?vlog("cache gc trigger event", []),
- maybe_gc_cache(Cache, Age, GcLimit),
+ gcvprint(GcVerbose, "GC: begin"),
+ case maybe_gc_cache(Cache, Age, GcLimit) of
+ {ok, NumDeleted} = Result when (NumDeleted > 0) ->
+ gcvprint(GcVerbose,
+ "GC: ~w elements deleted from cache", [NumDeleted]),
+ maybe_send_gc_result(S, Result);
+ _ ->
+ ok
+ end,
Tmr = start_cache_gc_timer(),
{noreply, S#state{cache_tmr = Tmr}};
@@ -749,14 +806,15 @@ get_cacheopt_autogc(Cache, CacheOpts) ->
IsValid).
get_cacheopt_gclimit(Cache, CacheOpts) ->
- IsValid = fun(Limit) when ((is_integer(Limit) andalso (Limit > 0)) orelse
- (Limit =:= infinity)) ->
+ IsValid = fun(Limit) when ((is_integer(Limit) andalso
+ (Limit > 0)) orelse
+ (Limit =:= infinity)) ->
true;
(_) ->
false
end,
- get_cacheopt(Cache, gclimit, CacheOpts,
- infinity, ?DEFAULT_CACHE_GCLIMIT,
+ get_cacheopt(Cache, gclimit, CacheOpts,
+ infinity, ?DEFAULT_CACHE_GCLIMIT,
IsValid).
get_cacheopt_age(Cache, CacheOpts) ->
@@ -765,8 +823,18 @@ get_cacheopt_age(Cache, CacheOpts) ->
(_) ->
false
end,
- get_cacheopt(Cache, age, CacheOpts,
- ?DEFAULT_CACHE_AGE, ?DEFAULT_CACHE_AGE,
+ get_cacheopt(Cache, age, CacheOpts,
+ ?DEFAULT_CACHE_AGE, ?DEFAULT_CACHE_AGE,
+ IsValid).
+
+get_cacheopt_gcverbose(Cache, CacheOpts) ->
+ IsValid = fun(Verbosity) when is_boolean(Verbosity) ->
+ true;
+ (_) ->
+ false
+ end,
+ get_cacheopt(Cache, gcverbose, CacheOpts,
+ ?DEFAULT_CACHE_GCVERBOSE, ?DEFAULT_CACHE_GCVERBOSE,
IsValid).
get_cacheopt(?NO_CACHE, _, _, NoCacheVal, _, _) ->
@@ -843,21 +911,28 @@ start_cache_gc_timer() ->
%% ----------------------------------------------------------------
+gcvprint(GcVerbose, F) ->
+ gcvprint(GcVerbose, F, []).
+
+gcvprint(true, F, A) ->
+ ?vinfo(F, A);
+gcvprint(_, _, _) ->
+ ok.
+
maybe_gc_cache(?NO_CACHE, _Age) ->
?vtrace("cache not enabled", []),
ok;
maybe_gc_cache(Cache, Age) ->
- MatchSpec = gc_cache_matchspec(Age),
- Keys = ets:select(Cache, MatchSpec),
- do_gc_cache(Cache, Keys),
- {ok, length(Keys)}.
+ MatchSpec = gc_cache_matchspec_del(Age),
+ NumDeleted = ets:select_delete(Cache, MatchSpec),
+ {ok, NumDeleted}.
maybe_gc_cache(?NO_CACHE, _Age, _GcLimit) ->
ok;
maybe_gc_cache(Cache, Age, infinity = _GcLimit) ->
maybe_gc_cache(Cache, Age);
maybe_gc_cache(Cache, Age, GcLimit) ->
- MatchSpec = gc_cache_matchspec(Age),
+ MatchSpec = gc_cache_matchspec_key(Age),
Keys =
case ets:select(Cache, MatchSpec, GcLimit) of
{Match, _Cont} ->
@@ -868,14 +943,26 @@ maybe_gc_cache(Cache, Age, GcLimit) ->
do_gc_cache(Cache, Keys),
{ok, length(Keys)}.
-gc_cache_matchspec(Age) ->
- Oldest = timestamp() - Age,
+gc_cache_matchspec_del(Age) ->
+ %% The entry is a 3-tuple: {Key, Value, Timestamp}
+ MatchHead = {'_', '_', '$2'},
+ Return = true,
+ gc_cache_matchspec(Age, MatchHead, Return).
+
+gc_cache_matchspec_key(Age) ->
+ %% The entry is a 3-tuple: {Key, Value, Timestamp}
MatchHead = {'$1', '_', '$2'},
+ Return = '$1',
+ gc_cache_matchspec(Age, MatchHead, Return).
+
+gc_cache_matchspec(Age, MatchHead, Return) ->
+ Oldest = timestamp() - Age,
Guard = [{'<', '$2', Oldest}],
- MatchFunc = {MatchHead, Guard, ['$1']},
+ MatchFunc = {MatchHead, Guard, [Return]},
MatchSpec = [MatchFunc],
MatchSpec.
+
do_gc_cache(_, []) ->
ok;
do_gc_cache(Cache, [Key|Keys]) ->
@@ -906,20 +993,37 @@ maybe_cache_lookup(?NO_CACHE, _) ->
maybe_cache_lookup(Cache, Key) ->
ets:lookup(Cache, Key).
-size_cache(?NO_CACHE) ->
+
+cache_info(?NO_CACHE) ->
undefined;
-size_cache(Cache) ->
- case (catch ets:info(Cache, memory)) of
- Sz when is_integer(Sz) ->
- Sz;
- _ ->
- undefined
+cache_info(Cache) ->
+ try
+ begin
+ [
+ {memory, ets:info(Cache, memory)},
+ {size, ets:info(Cache, size)},
+ {stats, ets:info(Cache, stats)}
+ ]
+ end
+ catch
+ _:_:_ ->
+ undefined
end.
+
timestamp() ->
snmp_misc:now(ms).
+maybe_send_gc_result(S, Result) ->
+ maybe_send_gc_event(S, gc_result, Result).
+
+maybe_send_gc_event(#state{cache_sub = Sub}, Ev, Info) when is_pid(Sub) ->
+ Sub ! {self(), Ev, Info};
+maybe_send_gc_event(_, _, _) ->
+ ok.
+
+
%% ----------------------------------------------------------------
get_opt(Key, Options) ->
diff --git a/lib/snmp/src/agent/snmpa_mpd.erl b/lib/snmp/src/agent/snmpa_mpd.erl
index 552b9671af..0d40840d35 100644
--- a/lib/snmp/src/agent/snmpa_mpd.erl
+++ b/lib/snmp/src/agent/snmpa_mpd.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2019. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -166,6 +166,19 @@ process_packet(Packet, From, LocalEngineID, State, NoteStore, Log) ->
NoteStore, Packet, From,
LocalEngineID, V3Hdr, Data, Log);
+ #message{version = MsgVersion} ->
+ ?vlog("Invalid Version: "
+ "~n Message Version: ~p"
+ "~nwhen"
+ "~n Versions:"
+ "~n v1: ~w"
+ "~n v2c: ~w"
+ "~n v3: ~w",
+ [MsgVersion,
+ State#state.v1, State#state.v2c, State#state.v3]),
+ inc(snmpInBadVersions),
+ {discarded, snmpInBadVersions};
+
{'EXIT', {bad_version, Vsn}} ->
?vtrace("exit: bad version: ~p",[Vsn]),
inc(snmpInBadVersions),
@@ -177,9 +190,11 @@ process_packet(Packet, From, LocalEngineID, State, NoteStore, Log) ->
{discarded, Reason};
UnknownMessage ->
- ?vtrace("Unknown message: ~n ~p"
- "~nwhen"
- "~n State: ~p", [UnknownMessage, State]),
+ ?vdebug("Unknown message: "
+ "~n ~p"
+ "~nwhen"
+ "~n State: "
+ "~n ~p", [UnknownMessage, State]),
inc(snmpInBadVersions),
{discarded, snmpInBadVersions}
end.
diff --git a/lib/snmp/src/agent/snmpa_net_if.erl b/lib/snmp/src/agent/snmpa_net_if.erl
index 4ecf833963..835b8d4375 100644
--- a/lib/snmp/src/agent/snmpa_net_if.erl
+++ b/lib/snmp/src/agent/snmpa_net_if.erl
@@ -36,27 +36,109 @@
-include("snmp_debug.hrl").
-include("snmp_verbosity.hrl").
+
+%% Regarding Trap/Notification transport(s),
+%% it *should* be possible to specify either:
+%% 1) A fixed set of transport(s) used for sending.
+%% For instance, one for IPv4 and one for IPv6.
+%% 2) A single one-shot ephemeral port.
+%% That is, a port is created, used once, and then closed.
+%% 3) A pool of ephemeral ports, used for "a time"
+%% (a configurable number of sends, a set number of bytes
+%% or time based) thar is cycled through.
+%% Though, we do *not* currently implement ephemeral sockets,
+%% so case 2 and 3 are not possible at the moment.
+
+%% Also, the request-reponder and trap-sender transport(s),
+%% has different needs.
+%% The trap-sender transport will send more data then it will receive.
+%% Therefor, we should be able to specify;
+%% bind_to, no_reuse_address, recbuf and sndbuf individually:
+%% {intAgentTransports,
+%% [{transportDomainUdpIpv4, {{141,213,11,24}, PortInfo},
+%% req_responder, Opts},
+%% {transportDomainUdpIpv4, {{141,213,11,24}, PortInfo},
+%% trap_sender, Opts},
+%% {transportDomainUdpIpv6, {{0,0,0,0,0,0,0,1}, Portinfo},
+%% req_responder, Opts},
+%% {transportDomainUdpIpv6, {{0,0,0,0,0,0,0,1}, Portinfo},
+%% trap_sender, Opts}]}.
+%% Opts is basically "socket options" for this transport (the same
+%% options as for net-if).
+%% Also, always give the option to provide a port range:
+%% Port :: pos_integer() | system | range() || ranges()
+%% system => Let the system choose
+%% (0 is unfortunately already used as 'default',
+%% so we can't use that for 'system').
+%% range() :: {Min :: pos_integer(), Max :: pos_integer()} when Min < Max
+%% ranges() :: [pos_integer() | range()]
+%% Examples:
+%% [{2000, 2004}]
+%% [2000, 2001, 2002, 2003, 2004]
+%% [2000, 2001, {2002, 2004}]
+%% [{5000, 5100}, {6000, 6100}]
+
+%% <EPHEMERAL-FOR-FUTUR-USE>
+%% , *but*
+%% may also contain the tuple, {ephemeral, EphmOpts}.
+%% Ephm sockets are created on the fly, used for the specified
+%% time (number of sends, number of bytes sent, used time, ...).
+%% </EPHEMERAL-FOR-FUTUR-USE>
+
-record(state,
{parent,
note_store,
master_agent,
- transports = [],
-%% usock,
-%% usock_opts,
+ transports = [],
mpd_state,
log,
reqs = [],
debug = false,
limit = infinity,
-%% rcnt = [],
filter}).
-%% domain = snmpUDPDomain}).
+
+-type transport_kind() :: req_responder | trap_sender.
+-type port_info() :: non_neg_integer() |
+ system |
+ {pos_integer(), pos_integer()}.
+
+%% <EPHEMERAL-FOR-FUTUR-USE>
+%% How would 'ephemeral' effect this?
+%% What kind of usage would we have for emphemeral ports?
+%% once (send and maybe receive reply (inform)) |
+%% {sends, pos_integer()} (number of sends) |
+%% {data, pos_integer()} (number of bytes sent) |
+%% {alive_time, pos_integer()} (once used for the first time, alive time)
+%% You can't combine a ephemeral info with a fixed port (pos_integer())
+%% The port_info() is used when creating a port, even an ephemeral port.
+%% But it must either be 'system' or a range in that (ephemeral) case.
+%% Also, ephemeral transports are only allowed if kind = trap_sender
+%% -type ephemeral() :: none |
+%% once |
+%% {sends, pos_integer()} |
+%% {data, pos_integer()} |
+%% {alive_time, pos_integer()}.
+
+%% Note that since informs require confirmation,
+%% an ephemeral socket cannot be removed immediately
+%% when it has been "used up".
+%% We need to keep it for some time to receive responces
+%% and in case a resend is needed!.
+%% </EPHEMERAL-FOR-FUTUR-USE>
-record(transport,
{socket,
- domain = snmpUDPDomain,
- opts = [],
- req_refs = []}).
+ kind = all :: all | transport_kind(),
+ domain = snmpUDPDomain,
+ port_no :: pos_integer(),
+ port_info :: port_info(),
+ %% <EPHEMERAL-FOR-FUTUR-USE>
+ ephm = none, %% :: ephemeral(),
+ ephm_info = undefined, % Only used if ephm =/= none and once
+ %% </EPHEMERAL-FOR-FUTUR-USE>
+ opts = [],
+ req_refs = [] % Not used for trap/notification transports
+ }).
-ifndef(default_verbosity).
-define(default_verbosity,silence).
@@ -160,11 +242,11 @@ init(Prio, NoteStore, MasterAgent, Parent, Opts) ->
do_init(Prio, NoteStore, MasterAgent, Parent, Opts) ->
process_flag(trap_exit, true),
- process_flag(priority, Prio),
+ process_flag(priority, Prio),
%% -- Verbosity --
- put(sname,nif),
- put(verbosity,get_verbosity(Opts)),
+ put(sname, nif),
+ put(verbosity, get_verbosity(Opts)),
?vlog("starting",[]),
%% -- Versions --
@@ -182,22 +264,48 @@ do_init(Prio, NoteStore, MasterAgent, Parent, Opts) ->
Log = create_log(),
?vdebug("Log: ~w",[Log]),
- DomainAddresses = get_transports(),
- ?vdebug("DomainAddresses: ~w",[DomainAddresses]),
+ RawTransports = get_transports(),
+ ?vdebug("Raw Transports: "
+ "~n ~p", [RawTransports]),
try
[begin
- SocketOpts = socket_opts(Domain, Address, Opts),
- Socket = socket_open(Domain, SocketOpts),
+ %% Any socket option not explicitly configured for the transport
+ %% will be taken from the "global" socket options (which serve as
+ %% default values).
+ %% Also, note that Ephm are not actually used at this time.
+ {Ephm, PortInfo, SocketOpts} = socket_opts(Domain, Address,
+ RawSocketOpts, Opts),
+ ?vtrace("socket opts processed:"
+ "~n Ephm: ~p"
+ "~n Port Info: ~p"
+ "~n Socket Opts: ~p", [Ephm, PortInfo, SocketOpts]),
+ {Socket, IpPort} = socket_open(Domain, PortInfo,
+ SocketOpts),
+ ?vtrace("socket opened:"
+ "~n Socket: ~p"
+ "~n Port No: ~p", [Socket, IpPort]),
+ %% Should we really do this here?
+ %% If Kind =:= trap_sender, we only need to receive after
+ %% we have sent an inform!
active_once(Socket),
#transport{
- socket = Socket,
- domain = Domain,
- opts = SocketOpts}
- end || {Domain, Address} <- DomainAddresses]
+ socket = Socket,
+ kind = Kind,
+ domain = Domain,
+ %% We may not have explicitly specified the port ('system'
+ %% or a range), so it could have been "generated".
+ %% Also, shall we push this into the transport (handled by the
+ %% FRAMEWORK MIB)? Would not work for ephemeral sockets.
+ port_no = IpPort,
+ port_info = PortInfo,
+ ephm = Ephm,
+ opts = SocketOpts}
+ %% We need to fix this also
+ end || {Domain, Address, Kind, RawSocketOpts} <- RawTransports]
of
[] ->
- ?vinfo("No transports configured: ~p", [DomainAddresses]),
- {error, {no_transports,DomainAddresses}};
+ ?vinfo("No transports configured: ~p", [RawTransports]),
+ {error, {no_transports, RawTransports}};
Transports ->
MpdState = snmpa_mpd:init(Vsns),
init_counters(),
@@ -291,45 +399,158 @@ format_address(Address) ->
iolist_to_binary(snmp_conf:mk_addr_string(Address)).
-socket_open(snmpUDPDomain = Domain, [IpPort | Opts]) ->
+socket_open(snmpUDPDomain = Domain, IpPort, Opts) ->
+ ?vdebug("socket_open(~p) -> entry with"
+ "~n Port: ~p"
+ "~n Opts: ~p", [Domain, IpPort, Opts]),
case init:get_argument(snmp_fd) of
{ok, [[FdStr]]} ->
- Fd = list_to_integer(FdStr),
- ?vdebug("socket_open(~p, [~p | ~p]) Fd: ~p",
- [Domain, IpPort, Opts, Fd]),
- gen_udp_open(0, [{fd, Fd} | Opts]);
+ FD = list_to_integer(FdStr),
+ ?vdebug("socket_open(~p) -> open with fd - "
+ "(old) snmp_fd command line argument provided: "
+ "~n FD: ~p"
+ "~n Port: ~p"
+ "~n Opts: ~p", [Domain, FD, IpPort, Opts]),
+ gen_udp_open(0, [{fd, FD} | Opts]);
error ->
case init:get_argument(snmpa_fd) of
{ok, [[FdStr]]} ->
- Fd = list_to_integer(FdStr),
- ?vdebug("socket_open(~p, [~p | ~p]) Fd: ~p",
- [Domain, IpPort, Opts, Fd]),
- gen_udp_open(0, [{fd, Fd} | Opts]);
+ FD = list_to_integer(FdStr),
+ ?vdebug("socket_open(~p) -> open with fd - "
+ "snmpa_fd command line argument provided: "
+ "~n FD: ~p"
+ "~n Port: ~p"
+ "~n Opts: ~p", [Domain, FD, IpPort, Opts]),
+ gen_udp_open(0, [{fd, FD} | Opts]);
error ->
- ?vdebug("socket_open(~p, [~p | ~p])",
- [Domain, IpPort, Opts]),
+ ?vdebug("socket_open(~p) -> plain open"
+ "~n Port: ~p"
+ "~n Opts: ~p", [Domain, IpPort, Opts]),
gen_udp_open(IpPort, Opts)
end
end;
-socket_open(Domain, [IpPort | Opts])
- when Domain =:= transportDomainUdpIpv4;
- Domain =:= transportDomainUdpIpv6 ->
- ?vdebug("socket_open(~p, [~p | ~p])", [Domain, IpPort, Opts]),
- gen_udp_open(IpPort, Opts);
-socket_open(Domain, Opts) ->
+socket_open(Domain, PortInfo, Opts)
+ when (Domain =:= transportDomainUdpIpv4) orelse
+ (Domain =:= transportDomainUdpIpv6) ->
+ ?vdebug("socket_open(~p) -> entry with"
+ "~n PortInfo: ~p"
+ "~n Opts: ~p", [Domain, PortInfo, Opts]),
+ gen_udp_open(PortInfo, Opts);
+socket_open(Domain, PortInfo, Opts) ->
+ ?vinfo("socket_open(~p) -> entry when invalid with"
+ "~n PortInfo: ~p"
+ "~n Opts: ~p", [Domain, PortInfo, Opts]),
throw({socket_open, Domain, Opts}).
-gen_udp_open(IpPort, Opts) ->
+
+%% Make the system choose!
+gen_udp_open(system, Opts) ->
+ ?vtrace("gen_udp_open(system) -> entry"),
+ case gen_udp:open(0, Opts) of
+ {ok, Socket} ->
+ case inet:port(Socket) of
+ {ok, PortNo} ->
+ ?vtrace("gen_udp_open(system) -> created: "
+ "~n ~p (~w)", [Socket, PortNo]),
+ {Socket, PortNo};
+ {error, PReason} ->
+ (catch gen_udp:close(Socket)),
+ throw({udp_open, {port, PReason}})
+ end;
+ {error, OReason} ->
+ throw({udp_open, {open, OReason}})
+ end;
+%% This is for "future compat" since we cannot actually config '0'...
+gen_udp_open(IpPort, Opts) when (IpPort =:= 0) ->
+ ?vtrace("gen_udp_open(0) -> entry"),
case gen_udp:open(IpPort, Opts) of
{ok, Socket} ->
- Socket;
+ case inet:port(Socket) of
+ {ok, PortNo} ->
+ ?vtrace("gen_udp_open(0) -> created: "
+ "~n ~p (~w)", [Socket, PortNo]),
+ {Socket, PortNo};
+ {error, PReason} ->
+ (catch gen_udp:close(Socket)),
+ throw({udp_open, {port, PReason}})
+ end;
{error, Reason} ->
- throw({udp_open, IpPort, Reason})
+ throw({udp_open, {open, IpPort, Reason}})
+ end;
+gen_udp_open(IpPort, Opts) when is_integer(IpPort) ->
+ ?vtrace("gen_udp_open(~w) -> entry", [IpPort]),
+ case gen_udp:open(IpPort, Opts) of
+ {ok, Socket} ->
+ ?vtrace("gen_udp_open(~w) -> created: "
+ "~n ~p", [Socket]),
+ {Socket, IpPort};
+ {error, Reason} ->
+ throw({udp_open, {open, IpPort, Reason}})
+ end;
+%% A range is "pointless" if we allow reuseaddr...
+%% ...but we leave that to the user...
+gen_udp_open({Min, Max}, Opts) ->
+ ?vtrace("gen_udp_open(~w,~w) -> entry", [Min, Max]),
+ gen_udp_range_open(Min, Max, Opts);
+%% A range is "pointless" if we allow reuseaddr...
+%% ...but we leave that to the user...
+gen_udp_open(Ranges, Opts) when is_list(Ranges) ->
+ gen_udp_ranges_open(Ranges, Opts).
+
+gen_udp_range_open(Min, Max, _Opts) when (Min > Max) ->
+ ?vinfo("gen_udp_range_open -> entry when no available ports"),
+ throw({udp_open, no_available_ports});
+gen_udp_range_open(Min, Max, Opts) ->
+ ?vtrace("gen_udp_range_open -> entry with"
+ "~n Min: ~w"
+ "~n Max: ~w", [Min, Max]),
+ try gen_udp:open(Min, Opts) of
+ {ok, Socket} ->
+ ?vtrace("gen_udp_range_open(~w,~w) -> created: "
+ "~n ~p", [Min, Max, Socket]),
+ {Socket, Min};
+ {error, eaddrinuse} ->
+ ?vdebug("gen_udp_range_open(~w,~w) -> eaddrinuse"),
+ gen_udp_range_open(Min+1, Max, Opts);
+ {error, Reason} ->
+ ?vdebug("gen_udp_range_open(~w,~w) -> ~w", [Reason]),
+ throw({udp_open, {open, Reason}})
+ catch
+ C:E:S ->
+ ?vinfo("gen_udp_range_open(~w,~w) -> failed open socket: "
+ "~n C: ~p"
+ "~n E: ~p"
+ "~n S: ~p", [Min, Max, C, E, S]),
+ erlang:raise(C, E, S)
end.
+gen_udp_ranges_open([], _Opts) ->
+ ?vinfo("gen_udp_ranges_open -> entry when no available ports"),
+ throw({udp_open, no_available_ports});
+gen_udp_ranges_open([PortNo|Ranges], Opts) when is_integer(PortNo) andalso
+ (PortNo > 0) ->
+ ?vtrace("gen_udp_ranges_open(~w) -> entry", [PortNo]),
+ try gen_udp_open(PortNo, Opts) of
+ {_Sock, PortNo} = SUCCESS when is_integer(PortNo) ->
+ SUCCESS
+ catch
+ throw:{udp_open, _} ->
+ gen_udp_ranges_open(Ranges, Opts)
+ end;
+gen_udp_ranges_open([{Min, Max}|Ranges], Opts) ->
+ ?vtrace("gen_udp_ranges_open(~w,~w) -> entry", [Min, Max]),
+ try gen_udp_range_open(Min, Max, Opts) of
+ {_Sock, PortNo} = SUCCESS when is_integer(PortNo) ->
+ SUCCESS
+ catch
+ throw:{udp_open, _} ->
+ gen_udp_ranges_open(Ranges, Opts)
+ end.
-loop(#state{transports = Transports, limit = Limit, parent = Parent} = S) ->
+loop(#state{transports = Transports,
+ limit = Limit,
+ parent = Parent} = S) ->
?vdebug("loop(~p)", [S]),
receive
{udp, Socket, IpAddr, IpPort, Packet} = Msg when is_port(Socket) ->
@@ -349,6 +570,15 @@ loop(#state{transports = Transports, limit = Limit, parent = Parent} = S) ->
loop(S)
end;
+ {udp_error, Socket, Error} when is_port(Socket) ->
+ ?vinfo("got udp-error on ~p: ~w", [Socket, Error]),
+ case lists:keyfind(Socket, #transport.socket, Transports) of
+ #transport{socket = Socket} = Transport ->
+ loop(handle_udp_error(S, Transport, Error));
+ false ->
+ loop(handle_udp_error_unknown(S, Socket, Error))
+ end;
+
{info, ReplyRef, Pid} ->
Info = get_info(S),
Pid ! {ReplyRef, Info, self()},
@@ -363,12 +593,11 @@ loop(#state{transports = Transports, limit = Limit, parent = Parent} = S) ->
case
case
(Limit =/= infinity) andalso
- select_transport_from_req_ref(ReqRef, Transports)
+ select_transport(ReqRef, Transports)
of
false ->
- select_transport_from_domain(
- address_to_domain(To),
- Transports);
+ select_transport(address_to_domain(To), Type,
+ Transports);
T ->
T
end
@@ -463,7 +692,7 @@ loop(#state{transports = Transports, limit = Limit, parent = Parent} = S) ->
loop(update_req_counter_outgoing(S, false, ReqRef));
true ->
case
- select_transport_from_req_ref(ReqRef, Transports)
+ select_transport(ReqRef, Transports)
of
false ->
error_msg(
@@ -519,27 +748,31 @@ loop(#state{transports = Transports, limit = Limit, parent = Parent} = S) ->
"~n ~p", [Parent, Reason]),
exit(Reason);
+ %% We should not do this.
+ %% Future versions of sockets will/may not be linkable (port)
{'EXIT', Socket, Reason} when is_port(Socket) ->
case lists:keyfind(Socket, #transport.socket, Transports) of
#transport{
- socket = Socket,
- domain = Domain,
- opts = SocketOpts,
- req_refs = ReqRefs} = Transport ->
- try socket_open(Domain, SocketOpts) of
- NewSocket ->
+ socket = Socket,
+ domain = Domain,
+ port_info = PortInfo,
+ opts = SocketOpts,
+ req_refs = ReqRefs} = Transport ->
+ try socket_open(Domain, PortInfo, SocketOpts) of
+ {NewSocket, PortNo} ->
error_msg(
"Socket ~p exited for reason"
"~n ~p"
- "~n Re-opened (~p)",
- [Socket, Reason, NewSocket]),
+ "~n Re-opened (~p, ~w)",
+ [Socket, Reason, NewSocket, PortNo]),
(length(ReqRefs) < Limit) andalso
active_once(NewSocket),
S#state{
transports =
lists:keyreplace(
Socket, #transport.socket, Transports,
- Transport#transport{socket = NewSocket})}
+ Transport#transport{socket = NewSocket,
+ port_no = PortNo})}
catch
ReopenReason ->
error_msg(
@@ -572,6 +805,47 @@ loop(#state{transports = Transports, limit = Limit, parent = Parent} = S) ->
end.
+handle_udp_error(S, #transport{socket = Socket,
+ kind = Kind}, Error) ->
+ try inet:sockname(Socket) of
+ {ok, {IP, Port}} ->
+ error_msg("UDP Error for transport: "
+ "~n Socket: ~p (~p, ~p)"
+ "~n Kind: ~p"
+ "~n Error: ~p", [Socket, IP, Port, Kind, Error]);
+ {error, _} ->
+ error_msg("UDP Error for transport: "
+ "~n Socket: ~p"
+ "~n Kind: ~p"
+ "~n Error: ~p", [Socket, Kind, Error])
+ catch
+ _:_:_ ->
+ error_msg("UDP Error for transport: "
+ "~n Socket: ~p"
+ "~n Kind: ~p"
+ "~n Error: ~p", [Socket, Kind, Error])
+ end,
+ S.
+
+handle_udp_error_unknown(S, Socket, Error) ->
+ try inet:sockname(Socket) of
+ {ok, {IP, Port}} ->
+ warning_msg("UDP Error for unknown transport: "
+ "~n Socket: ~p (~p, ~p)"
+ "~n Error: ~p", [Socket, IP, Port, Error]);
+ {error, _} ->
+ warning_msg("UDP Error for unknown transport: "
+ "~n Socket: ~p"
+ "~n Error: ~p", [Socket, Error])
+ catch
+ _:_:_ ->
+ warning_msg("UDP Error for transport: "
+ "~n Socket: ~p"
+ "~n Error: ~p", [Socket, Error])
+ end,
+ S.
+
+
update_req_counter_incoming(
#state{limit = infinity} = S,
#transport{socket = Socket},
@@ -615,7 +889,7 @@ update_req_counter_outgoing(
ReqRef) ->
LengthReqRefs = length(ReqRefs),
?vtrace("update_req_counter_outgoing() -> entry with~n"
- " Limit: ~w~n"
+ " Limit: ~w~n"
" ReqRef: ~w~n"
" length(ReqRefs): ~w", [Limit, ReqRef, LengthReqRefs]),
NewReqRefs = lists:delete(ReqRef, ReqRefs),
@@ -654,7 +928,9 @@ maybe_handle_recv(
end
of
false ->
- %% Drop the received packet
+ %% Drop the received packet
+ %% What if this is an expected (inform) reply
+ %% on an ephemeral socket?
inc(netIfMsgInDrops),
active_once(Socket),
S;
@@ -909,13 +1185,13 @@ handle_reply_pdu(
-maybe_handle_send_pdu(
- #state{filter = FilterMod, transports = Transports} = S,
- Vsn, Pdu, MsgData, TDomAddrSecs, From) ->
-
- ?vtrace("maybe_handle_send_pdu -> entry with~n"
- " FilterMod: ~p~n"
- " TDomAddrSecs: ~p", [FilterMod, TDomAddrSecs]),
+maybe_handle_send_pdu(#state{filter = FilterMod,
+ transports = Transports} = S,
+ Vsn, Pdu, MsgData, TDomAddrSecs, From) ->
+
+ ?vtrace("maybe_handle_send_pdu -> entry with"
+ "~n FilterMod: ~p"
+ "~n TDomAddrSecs: ~p", [FilterMod, TDomAddrSecs]),
DomAddrSecs = snmpa_mpd:process_taddrs(TDomAddrSecs),
AddressesToFilter =
@@ -1022,7 +1298,7 @@ handle_send_discovery(
case (catch snmpa_mpd:generate_discovery_msg(NS, Pdu, MsgData, To)) of
{ok, {Domain, Address, Packet}} ->
- case select_transport_from_domain(Domain, Transports) of
+ case select_transport(Domain, Type, Transports) of
false ->
error_msg(
"Can not find transport to: ~s",
@@ -1066,16 +1342,21 @@ do_handle_send_pdu(S, Type, Pdu, Addresses) ->
[Sz, Reason, Pdu])
end.
-do_handle_send_pdu1(S, Type, Addresses) ->
- lists:foreach(
- fun ({Domain, Address, Pkg}) when is_binary(Pkg) ->
- do_handle_send_pdu2(S, Type, Domain, Address,
- Pkg, Pkg, "");
- ({Domain, Address, {Pkg, LogPkg}}) when is_binary(Pkg) ->
- do_handle_send_pdu2(S, Type, Domain, Address,
- Pkg, LogPkg, " encrypted")
- end,
- Addresses).
+%% Because of the ephemeral sockets used by some transports,
+%% the list of transports may be update for each send...
+do_handle_send_pdu1(S, _Type, []) ->
+ S;
+do_handle_send_pdu1(S, Type, [{Domain, Address, Pkg}|Addresses])
+ when is_binary(Pkg) ->
+ NewS = do_handle_send_pdu2(S, Type, Domain, Address,
+ Pkg, Pkg, ""),
+ do_handle_send_pdu1(NewS, Type, Addresses);
+do_handle_send_pdu1(S, Type, [{Domain, Address, {Pkg, LogPkg}}|Addresses])
+ when is_binary(Pkg) ->
+ NewS = do_handle_send_pdu2(S, Type, Domain, Address,
+ Pkg, LogPkg, " encrypted"),
+ do_handle_send_pdu1(NewS, Type, Addresses).
+
do_handle_send_pdu2(#state{transports = Transports} = S,
Type, Domain, Address, Pkg, LogPkg, EncrStr) ->
@@ -1083,17 +1364,207 @@ do_handle_send_pdu2(#state{transports = Transports} = S,
"~n size: ~p"
"~n to: ~p", [Domain, EncrStr, sz(Pkg), Address]),
To = {Domain, Address},
- case select_transport_from_domain(Domain, Transports) of
+ case select_transport(Domain, Type, Transports) of
false ->
- error_msg("Can not find transport: "
+ error_msg("Transport not found: "
"~n size: ~p"
"~n to: ~s",
- [sz(Pkg), format_address(To)]);
- Transport ->
- maybe_udp_send_w_log(S, Transport, To, Pkg, LogPkg, Type)
+ [sz(Pkg), format_address(To)]),
+ S;
+ #transport{ephm = none} = Transport ->
+ ?vtrace("do_handle_send_pdu2 -> transport(ephm = none) selected: "
+ "~n ~p", [Transport]),
+ maybe_udp_send_w_log(S, Transport, To, Pkg, LogPkg, Type),
+ S%;
+
+ %% <EPHEMERAL-FOR-FUTUR-USE>
+ %% #transport{} = Transport ->
+ %% ?vtrace("do_handle_send_pdu2 -> transport selected: "
+ %% "~n ~p", [Transport]),
+ %% case maybe_udp_send_w_log(S, Transport, To, Pkg, LogPkg, Type) of
+ %% {ok, Sz} -> % we actually sent something
+ %% maybe_update_ephm_transport(S, Transport, Type, Sz);
+ %% _ -> % Non-fatal error -> Nothing sent
+ %% S
+ %% end
+ %% </EPHEMERAL-FOR-FUTUR-USE>
end.
+%% <EPHEMERAL-FOR-FUTUR-USE>
+
+%% For inform:
+%% This will have a reply, so we cannot close it directly!
+%% Also, we will resend (a couple of times), if we don't
+%% get a reply in time.
+%% So, how do we handle this transport in this case?
+%% Shall we create a list of used sockets? Tagged with a key
+%% so we can reuse the correct socket for a resend?
+
+%%
+%% Or shall we used the same socket for all the sends for this
+%% trap/inform? That is, we send the trap/inform to a number of
+%% targets (the Addresses list), and *that* is considered *one*
+%% use?
+%% That would mean that we would potentially need to wait for
+%% replies from a large number of targets?
+%% But it may be better in the long term, because we will not
+%% use of so many sockets.
+%%
+
+%% maybe_update_ephm_transport(S, #transport{ephm = once} = _Transport,
+%% 'inform-request' = _Type, _Sz) ->
+%% S; % Figure out the above first!
+
+%% %% Before we close the current socket, create the new.
+%% %% This is done in case we fail to create a new socket
+%% %% (if we first close the current and then fail to create
+%% %% the new, we are stuck).
+%% %% If we fail to create the new socket, we keep the current.
+%% %% Better then nothing!
+%% maybe_update_ephm_transport(S, #transport{socket = OldSocket,
+%% ephm = once,
+%% port_info = PortInfo,
+%% opts = Opts} = Transport,
+%% _Type, _Sz) ->
+%% try
+%% begin
+%% {Socket, PortNo} = gen_udp_open(PortInfo, Opts),
+%% (catch gen_udp:close(OldSocket)),
+%% T2 = Transport#transport{socket = Socket,
+%% port_no = PortNo},
+%% TS = S#state.transports,
+%% TS2 = lists:keyreplace(OldSocket, #transport.socket, TS, T2),
+%% S#state{transports = TS2}
+%% end
+%% catch
+%% _:_:_ ->
+%% %% We need to identify which transport!
+%% error_msg("Failed creating new ephemeral socket for transport"),
+%% S
+%% end;
+
+%% %% Note that we do not currently handle inform:s, as that adds a whole
+%% %% set of issues. See above for more info.
+%% maybe_update_ephm_transport(S, #transport{socket = Socket,
+%% ephm = {sends, MaxSends},
+%% ephm_info = NumSends,
+%% port_info = _PortInfo,
+%% opts = _Opts} = Transport,
+%% _Type, _Sz) when (MaxSends > NumSends) ->
+%% T2 = Transport#transport{ephm_info = NumSends + 1},
+%% TS = S#state.transports,
+%% TS2 = lists:keyreplace(Socket, #transport.socket, TS, T2),
+%% S#state{transports = TS2};
+%% maybe_update_ephm_transport(S, #transport{socket = OldSocket,
+%% ephm = {sends, _MaxSends},
+%% ephm_info = _NumSends,
+%% port_info = PortInfo,
+%% opts = Opts} = Transport,
+%% _Type, _Sz) ->
+%% try
+%% begin
+%% {Socket, PortNo} = gen_udp_open(PortInfo, Opts),
+%% (catch gen_udp:close(OldSocket)),
+%% T2 = Transport#transport{socket = Socket,
+%% ephm_info = 0,
+%% port_no = PortNo},
+%% TS = S#state.transports,
+%% TS2 = lists:keyreplace(OldSocket, #transport.socket, TS, T2),
+%% S#state{transports = TS2}
+%% end
+%% catch
+%% _:_:_ ->
+%% %% We need to identify which transport!
+%% error_msg("Failed creating new ephemeral socket for transport"),
+%% S
+%% end;
+
+%% %% Note that we do not currently handle inform:s, as that adds a whole
+%% %% set of issues. See above for more info.
+%% maybe_update_ephm_transport(S, #transport{socket = Socket,
+%% ephm = {data, MaxData},
+%% ephm_info = AccSent,
+%% port_info = _PortInfo,
+%% opts = _Opts} = Transport,
+%% _Type, Sz) when (MaxData > (AccSent + Sz)) ->
+%% T2 = Transport#transport{ephm_info = AccSent + Sz},
+%% TS = S#state.transports,
+%% TS2 = lists:keyreplace(Socket, #transport.socket, TS, T2),
+%% S#state{transports = TS2};
+%% maybe_update_ephm_transport(S, #transport{socket = OldSocket,
+%% ephm = {data, _MaxData},
+%% ephm_info = _AccSent,
+%% port_info = PortInfo,
+%% opts = Opts} = Transport,
+%% _Type, _Sz) ->
+%% try
+%% begin
+%% {Socket, PortNo} = gen_udp_open(PortInfo, Opts),
+%% (catch gen_udp:close(OldSocket)),
+%% T2 = Transport#transport{socket = Socket,
+%% ephm_info = 0,
+%% port_no = PortNo},
+%% TS = S#state.transports,
+%% TS2 = lists:keyreplace(OldSocket, #transport.socket, TS, T2),
+%% S#state{transports = TS2}
+%% end
+%% catch
+%% _:_:_ ->
+%% %% We need to identify which transport!
+%% error_msg("Failed creating new ephemeral socket for transport"),
+%% S
+%% end;
+
+%% %% Note that we do not currently handle inform:s, as that adds a whole
+%% %% set of issues. See above for more info.
+%% maybe_update_ephm_transport(S, #transport{socket = Socket,
+%% ephm = {alive_time, AliveTime},
+%% ephm_info = undefined} = Transport,
+%% _Type, _Sz) ->
+%% AliveEnd = erlang:monotonic_time(micro_seconds) + AliveTime,
+%% T2 = Transport#transport{ephm_info = AliveEnd},
+%% TS = S#state.transports,
+%% TS2 = lists:keyreplace(Socket, #transport.socket, TS, T2),
+%% S#state{transports = TS2};
+%% maybe_update_ephm_transport(S, #transport{socket = OldSocket,
+%% ephm = {alive_time, _AliveTime},
+%% ephm_info = AliveEnd,
+%% port_info = PortInfo,
+%% opts = Opts} = Transport,
+%% _Type, _Sz) ->
+%% TS = erlang:monotonic_time(micro_seconds),
+%% if
+%% (TS > AliveEnd) ->
+%% try
+%% begin
+%% {Socket, PortNo} = gen_udp_open(PortInfo, Opts),
+%% (catch gen_udp:close(OldSocket)),
+%% T2 = Transport#transport{socket = Socket,
+%% %% This will be set when the transport
+%% %% is first used
+%% ephm_info = undefined,
+%% port_no = PortNo},
+%% TS = S#state.transports,
+%% TS2 = lists:keyreplace(OldSocket, #transport.socket, TS, T2),
+%% S#state{transports = TS2}
+%% end
+%% catch
+%% _:_:_ ->
+%% %% We need to identify which transport!
+%% error_msg("Failed creating new ephemeral socket for transport"),
+%% S
+%% end;
+%% true ->
+%% S
+%% end;
+
+%% maybe_update_ephm_transport(S, _Transport, _Type, _Sz) ->
+%% S.
+
+%% </EPHEMERAL-FOR-FUTUR-USE>
+
+
%% This function is used when logging has already been done!
maybe_udp_send_wo_log(
#state{filter = FilterMod, transports = Transports},
@@ -1156,6 +1627,7 @@ maybe_udp_send_w_log(
udp_send(Socket, To, Pkg)
end.
+
udp_send(Socket, To, B) ->
{IpAddr, IpPort} =
case To of
@@ -1171,9 +1643,11 @@ udp_send(Socket, To, B) ->
{error, ErrorReason} ->
error_msg("[error] cannot send message "
"(destination: ~p:~p, size: ~p, reason: ~p)",
- [IpAddr, IpPort, sz(B), ErrorReason]);
+ [IpAddr, IpPort, sz(B), ErrorReason]),
+ ok;
ok ->
- ok
+ %% For future use! Ephemeral ports!
+ {ok, size(B)}
catch
error:ExitReason:StackTrace ->
error_msg("[exit] cannot send message "
@@ -1229,31 +1703,90 @@ active_once(Sock) ->
inet:setopts(Sock, [{active, once}]).
-select_transport_from_req_ref(_, []) ->
+select_transport(_, []) ->
false;
-select_transport_from_req_ref(
- ReqRef,
- [#transport{req_refs = ReqRefs} = Transport | Transports]) ->
+select_transport(ReqRef,
+ [#transport{req_refs = ReqRefs} = Transport | Transports]) ->
case lists:member(ReqRef, ReqRefs) of
true ->
Transport;
false ->
- select_transport_from_req_ref(ReqRef, Transports)
+ select_transport(ReqRef, Transports)
end.
-select_transport_from_domain(Domain, Transports) when is_atom(Domain) ->
- Pos = #transport.domain,
- case lists:keyfind(Domain, Pos, Transports) of
- #transport{domain = Domain} = Transport ->
- Transport;
- false when Domain == snmpUDPDomain ->
- lists:keyfind(transportDomainUdpIpv4, Pos, Transports);
- false when Domain == transportDomainUdpIpv4 ->
- lists:keyfind(snmpUDPDomain, Pos, Transports);
- false ->
- false
+select_transport(snmpUDPDomain = Domain, Type, Transports) ->
+ ?vtrace("select_transport -> entry with"
+ "~n Domain: ~p"
+ "~n Type: ~p"
+ "~n Transports: ~p",
+ [Domain, Type, Transports]),
+ case select_transport2(Domain, Type, Transports) of
+ #transport{} = Transport ->
+ ?vtrace("select_transport -> selected: "
+ "~n ~p",
+ [Transport]),
+ Transport;
+ false ->
+ select_transport2(transportDomainUdpIpv4, Type, Transports)
+ end;
+select_transport(Domain, Type, Transports)
+ when is_atom(Domain) ->
+ ?vtrace("select_transport -> entry with"
+ "~n Domain: ~p"
+ "~n Type: ~p"
+ "~n Transports: ~p",
+ [Domain, Type, Transports]),
+ case select_transport2(Domain, Type, Transports) of
+ #transport{} = Transport ->
+ ?vtrace("select_transport -> selected: "
+ "~n ~p",
+ [Transport]),
+ Transport;
+ false when Domain =:= transportDomainUdpIpv4 ->
+ ?vdebug("select_transport -> (~p) not found: "
+ "~n try ~p", [Domain, snmpUDPDomain]),
+ select_transport2(snmpUDPDomain, Type, Transports);
+ false ->
+ false
end.
+
+%% Two kinds of (pdu) type that we (the agent) will ever attempt to send:
+%% req-responder: 'get-response'
+%% trap-sender: 'inform-request' |
+%% 'snmpv2-trap' |
+%% report |
+%% trap (which is a 'fake' type (#trappdu{}))
+select_transport2(_Domain, _Type,
+ []) ->
+ false;
+select_transport2(snmpUDPDomain = Domain, _Type,
+ [#transport{domain = Domain} = Transport|_Transports]) ->
+ Transport;
+select_transport2(snmpUDPDomain = Domain, Type,
+ [_|Transports]) ->
+ select_transport2(Domain, Type, Transports);
+select_transport2(Domain, Type,
+ [#transport{domain = Domain,
+ kind = Kind} = Transport|_Transports])
+ when ((Type =:= 'inform-request') orelse
+ (Type =:= 'snmpv2-trap') orelse
+ (Type =:= report) orelse
+ (Type =:= trap) orelse
+ (Type =:= trappdu)) andalso
+ ((Kind =:= all) orelse (Kind =:= trap_sender)) ->
+ Transport;
+select_transport2(Domain, Type,
+ [#transport{domain = Domain,
+ kind = Kind} = Transport|_Transports])
+ when (Type =:= 'get-response') andalso
+ ((Kind =:= all) orelse (Kind =:= req_responder)) ->
+ Transport;
+select_transport2(Domain, Type, [_|Transports]) ->
+ select_transport2(Domain, Type, Transports).
+
+
+
address_to_domain({Domain, _Addr}) when is_atom(Domain) ->
Domain;
address_to_domain({_Ip, Port}) when is_integer(Port) ->
@@ -1446,47 +1979,62 @@ get_counters([Counter|Counters], Acc) ->
%% ----------------------------------------------------------------
-socket_opts(Domain, {IpAddr, IpPort}, Opts) ->
- [IpPort, % Picked off at socket open, separate argument
- binary
- | case snmp_conf:tdomain_to_family(Domain) of
- inet6 = Family ->
- [Family, {ipv6_v6only, true}];
- Family ->
- [Family]
- end ++
- case get_bind_to_ip_address(Opts) of
- true ->
- [{ip, IpAddr}];
- _ ->
- []
- end ++
- case get_no_reuse_address(Opts) of
- false ->
- [{reuseaddr, true}];
- _ ->
- []
- end ++
- case get_recbuf(Opts) of
- use_default ->
- [];
- Sz ->
- [{recbuf, Sz}]
- end ++
- case get_sndbuf(Opts) of
- use_default ->
- [];
- Sz ->
+%% This extracts the socket options from what is specified for
+%% the transport, or if not, from the "global" socket options.
+socket_opts(Domain, {IpAddr, PortInfo}, SocketOpts, DefaultOpts) ->
+ ?vtrace("socket_opts -> entry with"
+ "~n Domain: ~p"
+ "~n IpAddr: ~p"
+ "~n PortInfo: ~p"
+ "~n SpocketOpts: ~p"
+ "~n DefaultOpts: ~p",
+ [Domain, IpAddr, PortInfo, SocketOpts, DefaultOpts]),
+ Opts =
+ [binary |
+ case snmp_conf:tdomain_to_family(Domain) of
+ inet6 = Family ->
+ [Family, {ipv6_v6only, true}];
+ Family ->
+ [Family]
+ end ++
+ case get_bind_to_ip_address(SocketOpts, DefaultOpts) of
+ true ->
+ [{ip, IpAddr}];
+ _ ->
+ []
+ end ++
+ case get_no_reuse_address(SocketOpts, DefaultOpts) of
+ false ->
+ [{reuseaddr, true}];
+ _ ->
+ []
+ end ++
+ case get_recbuf(SocketOpts, DefaultOpts) of
+ use_default ->
+ [];
+ Sz ->
+ [{recbuf, Sz}]
+ end ++
+ case get_sndbuf(SocketOpts, DefaultOpts) of
+ use_default ->
+ [];
+ Sz ->
[{sndbuf, Sz}]
- end] ++
- case get_extra_sock_opts(Opts) of
+ end
+ ] ++
+ case get_extra_sock_opts(SocketOpts, DefaultOpts) of
ESO when is_list(ESO) ->
ESO;
BadESO ->
error_msg("Invalid 'extra socket options' (=> ignored):"
"~n ~p", [BadESO]),
[]
- end.
+ end,
+ %% <EPHEMERAL-FOR-FUTUR-USE>
+ %% Ephm = get_ephemeral(SocketOpts),
+ %% {Ephm, PortInfo, Opts}.
+ %% </EPHEMERAL-FOR-FUTUR-USE>
+ {none, PortInfo, Opts}.
%% ----------------------------------------------------------------
@@ -1528,27 +2076,47 @@ get_filter_opts(O) ->
get_filter_module(O) ->
snmp_misc:get_option(module, O, ?DEFAULT_FILTER_MODULE).
-get_recbuf(Opts) ->
- snmp_misc:get_option(recbuf, Opts, use_default).
+get_recbuf(Opts, DefaultOpts) ->
+ get_socket_opt(recbuf, Opts, DefaultOpts, use_default).
-get_sndbuf(Opts) ->
- snmp_misc:get_option(sndbuf, Opts, use_default).
+get_sndbuf(Opts, DefaultOpts) ->
+ get_socket_opt(sndbuf, Opts, DefaultOpts, use_default).
-get_no_reuse_address(Opts) ->
- snmp_misc:get_option(no_reuse, Opts, false).
+get_bind_to_ip_address(Opts, DefaultOpts) ->
+ get_socket_opt(bind_to, Opts, DefaultOpts, false).
-get_bind_to_ip_address(Opts) ->
- snmp_misc:get_option(bind_to, Opts, false).
+get_no_reuse_address(Opts, DefaultOpts) ->
+ get_socket_opt(no_reuse, Opts, DefaultOpts, false).
-get_extra_sock_opts(Opts) ->
- snmp_misc:get_option(extra_sock_opts, Opts, []).
+get_extra_sock_opts(Opts, DefaultOpts) ->
+ get_socket_opt(extra_sock_opts, Opts, DefaultOpts, []).
+
+%% <EPHEMERAL-FOR-FUTUR-USE>
+%% This is not realy a socket option, but rather socket 'meta'
+%% information. Its still put together with the actual socket
+%% options.
+%% get_ephemeral(SocketOpts) ->
+%% snmp_misc:get_option(ephemeral, SocketOpts, none).
+%% </EPHEMERAL-FOR-FUTUR-USE>
+
+
+get_socket_opt(Opt, Opts, DefaultOpts, DefaultVal) ->
+ snmp_misc:get_option(Opt, Opts,
+ snmp_misc:get_option(Opt, DefaultOpts, DefaultVal)).
+
%% ----------------------------------------------------------------
-error_msg(F,A) ->
+%% error_msg(F) ->
+%% error_msg(F, []).
+
+error_msg(F, A) ->
?snmpa_error("NET-IF server: " ++ F, A).
+warning_msg(F, A) ->
+ ?snmpa_warning("NET-IF server: " ++ F, A).
+
info_msg(F,A) ->
?snmpa_info("NET-IF server: " ++ F, A).
@@ -1581,9 +2149,11 @@ get_info(#state{transports = Transports, reqs = Reqs}) ->
Counters = get_counters(),
[{reqs, Reqs},
{counters, Counters},
- {process_memory, ProcSize}
- | [{port_info, get_port_info(Socket)}
- || #transport{socket = Socket} <- Transports]].
+ {process_memory, ProcSize},
+ {transport_info, [{PortNo, Kind, get_port_info(Socket)} ||
+ #transport{socket = Socket,
+ port_no = PortNo,
+ kind = Kind} <- Transports]}].
proc_mem(P) when is_pid(P) ->
case (catch erlang:process_info(P, memory)) of
@@ -1647,4 +2217,4 @@ get_port_info(Id) ->
BufSz.
-%% ----------------------------------------------------------------
+%% ---------------------------------------------------------------
diff --git a/lib/snmp/src/agent/snmpa_trap.erl b/lib/snmp/src/agent/snmpa_trap.erl
index 121a8c979c..9ee854b67d 100644
--- a/lib/snmp/src/agent/snmpa_trap.erl
+++ b/lib/snmp/src/agent/snmpa_trap.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -920,34 +920,53 @@ send_v1_trap(
do_send_v1_trap(Enter, Spec, V1Res, NVbs, ExtraInfo, NetIf, SysUpTime).
do_send_v1_trap(Enter, Spec, V1Res, NVbs, ExtraInfo, NetIf, SysUpTime) ->
+ ?vtrace("do_send_v1_trap -> try get transports"),
{value, Transports} = snmp_framework_mib:intAgentTransports(get),
- {_Domain, {AgentIp, _AgentPort}} =
- case lists:keyfind(snmpUDPDomain, 1, Transports) of
- false ->
- case lists:keyfind(transportDomainUdpIpv4, 1, Transports) of
- false ->
- ?vtrace(
- "snmpa_trap: cannot send v1 trap "
- "without IPv4 domain: ~p",
- [Transports]),
- user_err(
- "snmpa_trap: cannot send v1 trap "
- "without IPv4 domain: ~p",
- [Transports]);
- DomainAddr ->
- DomainAddr
- end;
- DomainAddr ->
- DomainAddr
- end,
- TrapPdu = make_v1_trap_pdu(Enter, Spec, NVbs, SysUpTime, AgentIp),
- AddrCommunities = mk_addr_communities(V1Res),
- lists:foreach(
- fun ({Community, Addrs}) ->
- ?vtrace("send v1 trap to ~p",[Addrs]),
- NetIf ! {send_pdu, 'version-1', TrapPdu,
- {community, Community}, Addrs, ExtraInfo}
- end, AddrCommunities).
+ ?vtrace("do_send_v1_trap -> transports: "
+ "~n ~p", [Transports]),
+ try
+ begin
+ {_Domain, {AgentIp, _AgentPort}, _Kind, _Opts} =
+ case lists:keyfind(snmpUDPDomain, 1, Transports) of
+ false ->
+ case lists:keyfind(transportDomainUdpIpv4, 1, Transports) of
+ false ->
+ ?vlog(
+ "do_send_v1_trap -> cannot send v1 trap "
+ "without IPv4 domain: ~p",
+ [Transports]),
+ user_err(
+ "snmpa_trap: cannot send v1 trap "
+ "without IPv4 domain: "
+ "~n ~p", [Transports]),
+ throw({error,
+ "Cannot send v1 trap without IPv4 domain"});
+ DomainAddr ->
+ ?vtrace("do_send_v1_trap -> found ~w transport:"
+ "~n ~p",
+ [transportDomainUdpIpv4, DomainAddr]),
+ DomainAddr
+ end;
+ DomainAddr ->
+ ?vtrace("do_send_v1_trap -> found ~w transport:"
+ "~n ~p",
+ [snmpUDPDomain, DomainAddr]),
+ DomainAddr
+ end,
+ TrapPdu = make_v1_trap_pdu(Enter, Spec, NVbs, SysUpTime, AgentIp),
+ AddrCommunities = mk_addr_communities(V1Res),
+ lists:foreach(
+ fun ({Community, Addrs}) ->
+ ?vtrace("do_send_v1_trap -> send v1 trap to ~p",[Addrs]),
+ NetIf ! {send_pdu, 'version-1', TrapPdu,
+ {community, Community}, Addrs, ExtraInfo}
+ end, AddrCommunities)
+ end
+ catch
+ throw:{error, _} = ERROR:_ ->
+ ERROR
+ end.
+
send_v2_trap(_TrapRec, [], _Vbs, _Recv, _ExtraInfo, _NetIf, _SysUpTime) ->
ok;
diff --git a/lib/snmp/src/agent/snmpa_usm.erl b/lib/snmp/src/agent/snmpa_usm.erl
index c3c16db332..6c660e1e8b 100644
--- a/lib/snmp/src/agent/snmpa_usm.erl
+++ b/lib/snmp/src/agent/snmpa_usm.erl
@@ -17,6 +17,7 @@
%%
%% %CopyrightEnd%
%%
+%% USM: RFC 3414
%% AES: RFC 3826
%%
@@ -502,8 +503,8 @@ generate_outgoing_msg(Message, SecEngineID, SecName, SecData, SecLevel,
{ScopedPduData, MsgPrivParams} =
encrypt(ScopedPduBytes, PrivProtocol, PrivKey, SecLevel,
MsgAuthEngineBoots, MsgAuthEngineTime),
- %% 3.1.5 - 3.1.7
- ?vtrace("generate_outgoing_msg -> [3.1.5 - 3.1.7]",[]),
+ %% 3.1.5, 3.1.7
+ ?vtrace("generate_outgoing_msg -> [3.1.5, 3.1.7]",[]),
UsmSecParams =
#usmSecurityParameters{msgAuthoritativeEngineID = SecEngineID,
msgAuthoritativeEngineBoots = MsgAuthEngineBoots,
diff --git a/lib/snmp/src/app/snmp.erl b/lib/snmp/src/app/snmp.erl
index 3e5115ab3f..e634890147 100644
--- a/lib/snmp/src/app/snmp.erl
+++ b/lib/snmp/src/app/snmp.erl
@@ -131,11 +131,11 @@
%% This is for XREF
-deprecated(
[
- {c, 1, "use snmpa:c/1 instead."},
- {c, 2, "use snmpa:c/2 instead."},
- {compile, 3, "use snmpa:compile/3 instead."},
- {is_consistent, 1, "use snmpa:is_consistent/1 instead."},
- {mib_to_hrl, 1, "use snmpa:mib_to_hrl/1 instead."},
+ {c, 1, "use snmpc:compile/1 instead."},
+ {c, 2, "use snmpc:compile/2 instead."},
+ {compile, 3, "use snmpc:compile/3 instead."},
+ {is_consistent, 1, "use snmpc:is_consistent/1 instead."},
+ {mib_to_hrl, 1, "use snmpc:mib_to_hrl/1 instead."},
{change_log_size, 1, "use snmpa:change_log_size/1 instead."},
{log_to_txt, 2, "use snmpa:log_to_txt/2 instead."},
diff --git a/lib/snmp/src/manager/snmpm_config.erl b/lib/snmp/src/manager/snmpm_config.erl
index aaa2aa0174..356ba44b08 100644
--- a/lib/snmp/src/manager/snmpm_config.erl
+++ b/lib/snmp/src/manager/snmpm_config.erl
@@ -363,7 +363,7 @@ register_agent(UserId, TargetName, Config0)
%% Check:
%% 1) That the mandatory configs are present
%% 2) That no illegal config, e.g. user_id (used internally),
- %% is not present
+ %% are present
%% 3) Check that there are no invalid or erroneous configs
%% 4) Check that the manager is capable of using the selected version
try
@@ -3118,7 +3118,7 @@ do_update_usm_user_info(Key,
{error, {unsupported_crypto, des_cbc}}
end;
do_update_usm_user_info(Key,
- #usm_user{priv = usmAesCfb128Protocoll} = User,
+ #usm_user{priv = usmAesCfb128Protocol} = User,
priv_key, Val)
when length(Val) =:= 16 ->
case is_crypto_supported(aes_cfb128) of
diff --git a/lib/snmp/src/manager/snmpm_net_if.erl b/lib/snmp/src/manager/snmpm_net_if.erl
index 5c18058a7a..1eb5ae216a 100644
--- a/lib/snmp/src/manager/snmpm_net_if.erl
+++ b/lib/snmp/src/manager/snmpm_net_if.erl
@@ -607,6 +607,19 @@ handle_info(
{noreply, State}
end;
+handle_info(
+ {udp_error, Socket, Error},
+ #state{transports = Transports} = State) ->
+ ?vinfo("got udp-error on ~p: ~w", [Socket, Error]),
+ case lists:keyfind(Socket, #transport.socket, Transports) of
+ #transport{socket = Socket} = Transport ->
+ handle_udp_error(Transport, Error),
+ {noreply, State};
+ false ->
+ handle_udp_error_unknown(Socket, Error),
+ {noreply, State}
+ end;
+
handle_info(inform_response_gc, State) ->
?vlog("received inform_response_gc message", []),
State2 = handle_inform_response_gc(State),
@@ -639,6 +652,41 @@ handle_info(Info, State) ->
handle_info_unknown(Info, State).
+handle_udp_error(#transport{socket = Socket}, Error) ->
+ try inet:sockname(Socket) of
+ {ok, {IP, Port}} ->
+ error_msg("UDP Error for transport: "
+ "~n Socket: ~p (~p, ~p)"
+ "~n Error: ~p", [Socket, IP, Port, Error]);
+ {error, _} ->
+ error_msg("UDP Error for transport: "
+ "~n Socket: ~p"
+ "~n Error: ~p", [Socket, Error])
+ catch
+ _:_:_ ->
+ error_msg("UDP Error for transport: "
+ "~n Socket: ~p"
+ "~n Error: ~p", [Socket, Error])
+ end.
+
+handle_udp_error_unknown(Socket, Error) ->
+ try inet:sockname(Socket) of
+ {ok, {IP, Port}} ->
+ warning_msg("UDP Error for unknown transport: "
+ "~n Socket: ~p (~p, ~p)"
+ "~n Error: ~p", [Socket, IP, Port, Error]);
+ {error, _} ->
+ warning_msg("UDP Error for unknown transport: "
+ "~n Socket: ~p"
+ "~n Error: ~p", [Socket, Error])
+ catch
+ _:_:_ ->
+ warning_msg("UDP Error for transport: "
+ "~n Socket: ~p"
+ "~n Error: ~p", [Socket, Error])
+ end.
+
+
handle_info_unknown(Info, State) ->
warning_msg("received unknown info: ~n~p", [Info]),
{noreply, State}.
diff --git a/lib/snmp/src/manager/snmpm_server.erl b/lib/snmp/src/manager/snmpm_server.erl
index 05bcd07462..ca18637b8d 100644
--- a/lib/snmp/src/manager/snmpm_server.erl
+++ b/lib/snmp/src/manager/snmpm_server.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2021. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -1612,16 +1612,17 @@ handle_snmp_error(#pdu{request_id = ReqId} = Pdu, Reason, State) ->
handle_snmp_error(CrapError, Reason, _State) ->
error_msg("received crap (snmp) error =>"
- "~n~p~n~p", [CrapError, Reason]),
+ "~n ~p"
+ "~n ~p", [CrapError, Reason]),
ok.
handle_snmp_error(Domain, Addr, ReqId, Reason, State) ->
- ?vtrace("handle_snmp_error -> entry with~n"
- " Domain: ~p~n"
- " Addr: ~p~n"
- " ReqId: ~p~n"
- " Reason: ~p", [Domain, Addr, ReqId, Reason]),
+ ?vtrace("handle_snmp_error -> entry with"
+ "~n Domain: ~p"
+ "~n Addr: ~p"
+ "~n ReqId: ~p"
+ "~n Reason: ~p", [Domain, Addr, ReqId, Reason]),
case snmpm_config:get_agent_user_id(Domain, Addr) of
{ok, UserId} ->
@@ -1629,24 +1630,24 @@ handle_snmp_error(Domain, Addr, ReqId, Reason, State) ->
{ok, UserMod, UserData} ->
handle_error(UserId, UserMod, Reason, ReqId,
UserData, State);
- _Error ->
+ _Error1 ->
case snmpm_config:user_info() of
{ok, DefUserId, DefMod, DefData} ->
handle_error(DefUserId, DefMod, Reason,
ReqId, DefData, State);
- _Error ->
+ _Error2 ->
error_msg("failed retreiving the default user "
"info handling snmp error "
"<~p,~p>: ~n~w~n~w",
[Domain, Addr, ReqId, Reason])
end
end;
- _Error ->
+ _Error3 ->
case snmpm_config:user_info() of
{ok, DefUserId, DefMod, DefData} ->
handle_error(DefUserId, DefMod, Reason,
ReqId, DefData, State);
- _Error ->
+ _Error4 ->
error_msg("failed retreiving the default user "
"info handling snmp error "
"<~p,~p>: ~n~w~n~w",
@@ -1679,10 +1680,10 @@ do_handle_error(Mod, ReqId, Reason, Data) ->
handle_snmp_pdu(#pdu{type = 'get-response', request_id = ReqId} = Pdu,
Domain, Addr, State) ->
- ?vtrace("handle_snmp_pdu(get-response) -> entry with~n"
- " Domain: ~p~n"
- " Addr: ~p~n"
- " Pdu: ~p", [Domain, Addr, Pdu]),
+ ?vtrace("handle_snmp_pdu(get-response) -> entry with"
+ "~n Domain: ~p"
+ "~n Addr: ~p"
+ "~n Pdu: ~p", [Domain, Addr, Pdu]),
case ets:lookup(snmpm_request_table, ReqId) of
@@ -1843,6 +1844,7 @@ handle_snmp_pdu(#pdu{type = 'get-response', request_id = ReqId} = Pdu,
end
end;
+
handle_snmp_pdu(CrapPdu, Domain, Addr, _State) ->
error_msg("received crap (snmp) Pdu from ~w:~w =>"
"~p", [Domain, Addr, CrapPdu]),
@@ -2093,7 +2095,7 @@ handle_snmp_trap(CrapTrap, Domain, Addr, _State) ->
do_handle_snmp_trap(SnmpTrapInfo, Domain, Addr, State) ->
case snmpm_config:get_agent_user_info(Domain, Addr) of
{ok, UserId, Target, RegType} ->
- ?vtrace("handle_snmp_trap -> found user: ~p", [UserId]),
+ ?vdebug("do_handle_snmp_trap -> found user: ~p", [UserId]),
case snmpm_config:user_info(UserId) of
{ok, Mod, Data} ->
handle_trap(
@@ -2105,7 +2107,7 @@ do_handle_snmp_trap(SnmpTrapInfo, Domain, Addr, State) ->
%% User no longer exists, unregister agent
?vlog("[trap] failed retreiving user info for "
"user ~p: "
- "~n ~p", [UserId, Error1]),
+ "~n ~p", [UserId, Error1]),
case snmpm_config:unregister_agent(UserId, Target) of
ok ->
%% Try use the default user
@@ -2131,8 +2133,8 @@ do_handle_snmp_trap(SnmpTrapInfo, Domain, Addr, State) ->
"failed unregister agent ~p <~p,~p> "
"belonging to non-existing "
"user ~p, handling trap: "
- "~n Error: ~w"
- "~n Trap info: ~w",
+ "~n Error: ~p"
+ "~n Trap info: ~p",
[Target, Domain, Addr, UserId,
Error3, SnmpTrapInfo])
end
@@ -2141,7 +2143,13 @@ do_handle_snmp_trap(SnmpTrapInfo, Domain, Addr, State) ->
Error4 ->
%% Unknown agent, pass it on to the default user
?vlog("[trap] failed retreiving user id for agent <~p,~p>: "
- "~n ~p", [Domain, Addr, Error4]),
+ "~n Error: ~p"
+ "~n when"
+ "~n Users: ~p"
+ "~n Agents: ~p",
+ [Domain, Addr, Error4,
+ snmpm_config:which_users(),
+ snmpm_config:which_agents()]),
case snmpm_config:user_info() of
{ok, DefUserId, DefMod, DefData} ->
handle_agent(
@@ -2152,8 +2160,9 @@ do_handle_snmp_trap(SnmpTrapInfo, Domain, Addr, State) ->
Error5 ->
error_msg(
"failed retreiving "
- "the default user info handling trap from "
- "<~p,~p>: ~n~w~n~w",
+ "the default user info, handling trap from <~p,~p>:"
+ "~n Error: ~p"
+ "~n Trap Info: ~p",
[Domain, Addr, Error5, SnmpTrapInfo])
end
end,
@@ -3513,6 +3522,7 @@ nis_stop(_) ->
ok.
+-dialyzer({nowarn_function, nis_info/1}).
nis_info(NIS) when is_pid(NIS) ->
NIS ! {?MODULE, self(), info},
receive
diff --git a/lib/snmp/src/misc/snmp_conf.erl b/lib/snmp/src/misc/snmp_conf.erl
index 7d91ac889b..39c18777c7 100644
--- a/lib/snmp/src/misc/snmp_conf.erl
+++ b/lib/snmp/src/misc/snmp_conf.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -51,6 +51,10 @@
check_ip/1, check_ip/2,
check_port/1,
%% ip_port_to_domaddr/2,
+ check_transport_address/2,
+ check_transport_kind/1,
+ check_transport_opts/1,
+ check_transport_port_ranges/1,
check_address/2, check_address/3,
check_taddress/2,
mk_taddress/1, mk_taddress/2,
@@ -805,6 +809,91 @@ mk_addr_string_ntoa(Domain, IP, Port) ->
io_lib:format(
"~s:~w", [mk_addr_string_ntoa(Domain, IP), Port])).
+
+%% ---------
+
+%% This is internal - can *not* be specified by the user.
+%% check_transport_kind(all) ->
+%% ok;
+check_transport_kind(req_responder) ->
+ ok;
+check_transport_kind(trap_sender) ->
+ ok;
+check_transport_kind(BadKind) ->
+ error({bad_transport_kind, BadKind}).
+
+
+
+%% ---------
+
+%% The options we accept are the same as for net-if!
+%% {bind_to, bind_to()} |
+%% {sndbuf, sndbuf()} |
+%% {recbuf, recbuf()} |
+%% {no_reuse, no_reuse()} |
+%% {extra_sock_opts, list()}
+%% bind_to() :: boolean()
+%% sndbuf() :: pos_integer()
+%% rcvbuf() :: pos_integer()
+%% no_reuse() :: boolean()
+%% <EPHEMERAL-FOR-FUTUR-USE>
+%% {ephemeral, ephemeral()} |
+%% ephemeral() :: none |
+%% once |
+%% {data, pos_integer()} |
+%% {sends, pos_integer()} |
+%% {alive_time, pos_integer()}
+%% </EPHEMERAL-FOR-FUTUR-USE>
+
+check_transport_opts(Opts) when is_list(Opts) ->
+ check_transport_opts(Opts, [], []);
+check_transport_opts(BadOpts) ->
+ error({bad_transport_opts, BadOpts}).
+
+check_transport_opts([], Extra, Acc) ->
+ lists:reverse(Acc) ++ Extra;
+check_transport_opts([{bind_to, BindTo} = Opt|Opts], Extra, Acc)
+ when is_boolean(BindTo) ->
+ check_transport_opts(Opts, Extra, [Opt|Acc]);
+check_transport_opts([{sndbuf, BufSz} = Opt|Opts], Extra, Acc)
+ when is_integer(BufSz) andalso (BufSz > 0) ->
+ check_transport_opts(Opts, Extra, [Opt | Acc]);
+check_transport_opts([{recbuf, BufSz} = Opt|Opts], Extra, Acc)
+ when is_integer(BufSz) andalso (BufSz > 0) ->
+ check_transport_opts(Opts, Extra, [Opt | Acc]);
+check_transport_opts([{no_reuse, NoReuse} = Opt|Opts], Extra, Acc)
+ when is_boolean(NoReuse) ->
+ check_transport_opts(Opts, Extra, [Opt|Acc]);
+%% <EPHEMERAL-FOR-FUTUR-USE>
+%% check_transport_opts([{ephemeral, Ephm} = Opt|Opts], Extra, Acc) ->
+%% check_transport_opt_ophm(Ephm),
+%% check_transport_opts(Opts, Extra, [Opt|Acc]);
+%% </EPHEMERAL-FOR-FUTUR-USE>
+check_transport_opts([{extra_sock_opts, Extra1} = Opt|Opts], Extra2, Acc)
+ when is_list(Extra1) andalso (Extra2 =:= []) ->
+ check_transport_opts(Opts, Extra1, [Opt|Acc]);
+check_transport_opts([H|_], _Extra, _Acc) ->
+ error({bad_transport_opts, H}).
+
+%% <EPHEMERAL-FOR-FUTUR-USE>
+%% check_transport_opt_ophm(none) ->
+%% ok;
+%% check_transport_opt_ophm(once) ->
+%% ok;
+%% check_transport_opt_ophm({data, DataSz})
+%% when is_integer(DataSz) andalso (DataSz > 0) ->
+%% ok;
+%% check_transport_opt_ophm({sends, Sends})
+%% when is_integer(Sends) andalso (Sends > 0) ->
+%% ok;
+%% check_transport_opt_ophm({alive_time, T})
+%% when is_integer(T) andalso (T > 0) ->
+%% ok;
+%% check_transport_opt_ophm(BadEphm) ->
+%% error({bad_transport_opts, {ephemeral, BadEphm}}).
+%% </EPHEMERAL-FOR-FUTUR-USE>
+
+
%% ---------
check_ip(X) ->
@@ -829,26 +918,69 @@ check_port(Port) when ?is_word(Port) ->
check_port(Port) ->
error({bad_port, Port}).
-%% ip_port_to_domaddr(IP, Port) when ?is_word(Port) ->
-%% %% XXX There is only code for IP domains here
-%% case check_address_ip(transportDomainUdpIpv4, IP) of
-%% false ->
-%% case check_address_ip(transportDomainUdpIpv6, IP) of
-%% false ->
-%% error({bad_address, {transportDomainUdpIpv4, {IP, Port}}});
-%% true ->
-%% {transportDomainUdpIpv6, {IP, Port}};
-%% FixedIP ->
-%% {transportDomainUdpIpv6, {FixedIP, Port}}
-%% end;
-%% true ->
-%% {transportDomainUdpIpv4, {IP, Port}};
-%% FixedIP ->
-%% {transportDomainUdpIpv4, {FixedIP, Port}}
-%% end;
-%% ip_port_to_domaddr(IP, Port) ->
-%% error({bad_address, {transportDomainUdpIpv4, {IP, Port}}}).
+check_transport_address(transportDomainUdpIpv4 = _Domain,
+ {{A0, A1, A2, A3}, PortInfo})
+ when ?is_ipv4_addr(A0, A1, A2, A3) ->
+ case PortInfo of
+ system ->
+ %% The actual port number will be choosen
+ %% by the system (create with port = 0)
+ %% when the socket is created.
+ true;
+ Port when ?is_word(Port) andalso (Port >= 0) ->
+ %% Note that the value 0, zero, has the normal meaning of
+ %% letting the system choose (that is the same effect as
+ %% using 'system').
+ true;
+ {Min, Max} when ?is_word(Min) andalso (Min > 0) andalso
+ ?is_word(Max) andalso (Max > Min) ->
+ true;
+ Ranges when is_list(Ranges) ->
+ check_transport_port_ranges(Ranges);
+ _ ->
+ false
+ end;
+check_transport_address(transportDomainUdpIpv6 = _Domain,
+ {{A0, A1, A2, A3, A4, A5, A6, A7}, PortInfo})
+ when ?is_ipv6_addr(A0, A1, A2, A3, A4, A5, A6, A7) ->
+ case PortInfo of
+ system ->
+ %% The actual port number will be choosen
+ %% by the system (create with port = 0)
+ %% when the socket is created.
+ true;
+ Port when ?is_word(Port) andalso (Port >= 0) ->
+ %% Note that the value 0, zero, has the normal meaning of
+ %% letting the system choose (that is the same effect as
+ %% using 'system').
+ true;
+ {Min, Max} when ?is_word(Min) andalso (Min > 0) andalso
+ ?is_word(Max) andalso (Max > Min) ->
+ true;
+ Ranges when is_list(Ranges) ->
+ check_transport_port_ranges(Ranges);
+ _ ->
+ false
+ end;
+check_transport_address(BadDomain, _) ->
+ error({bad_domain, BadDomain}).
+
+
+check_transport_port_ranges([]) ->
+ true;
+check_transport_port_ranges([PortNo|Ranges])
+ when ?is_word(PortNo) andalso (PortNo > 0) ->
+ check_transport_port_ranges(Ranges);
+check_transport_port_ranges([{Min, Max}|Ranges])
+ when ?is_word(Min) andalso (Min > 0) andalso
+ ?is_word(Max) andalso (Max > Min) ->
+ check_transport_port_ranges(Ranges);
+check_transport_port_ranges(_) ->
+ false.
+
+
+
%% Check a configuration term field from a file to see if it
%% can be fixed to be fed to mk_taddress/2.
diff --git a/lib/snmp/src/misc/snmp_config.erl b/lib/snmp/src/misc/snmp_config.erl
index 5aab9a74e0..43596f1e74 100644
--- a/lib/snmp/src/misc/snmp_config.erl
+++ b/lib/snmp/src/misc/snmp_config.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -47,7 +47,8 @@
write_agent_snmp_usm_conf/5,
write_agent_snmp_vacm_conf/3,
- write_manager_snmp_files/8,
+ write_manager_snmp_files/4, write_manager_snmp_files/5,
+ write_manager_snmp_files/7, write_manager_snmp_files/8,
write_manager_snmp_conf/4, write_manager_snmp_conf/5,
write_manager_snmp_users_conf/2,
write_manager_snmp_agents_conf/2,
@@ -500,6 +501,48 @@ config_agent_sys() ->
{Vsns, ConfigDir, SysConfig}.
+config_agent_transports(ID) ->
+ config_agent_transports(ID, []).
+
+config_agent_transports(ID, []) ->
+ i(ID ++ ". Configure atleast one transport: "),
+ T = config_agent_transport(ID),
+ config_agent_transports(ID, [T]);
+config_agent_transports(ID, Acc) ->
+ case ask(ID ++ ". Configure another transport (yes/no)?",
+ "yes", fun verify_yes_or_no/1) of
+ yes ->
+ T = config_agent_transport(ID),
+ config_agent_transports(ID, [T|Acc]);
+ no ->
+ lists:reverse(Acc)
+ end.
+
+config_agent_transport(ID) ->
+ TDomain = ask(ID ++ "a. Transport Domain "
+ "(UDP IPv4 (u4) or UDP IPv6 (u6))",
+ "u4", fun verify_transport_domain/1),
+ Host = host(TDomain),
+ Address = ask(ID ++ "b. Address of transport",
+ Host, fun(A) -> verify_transport_address(TDomain, A) end),
+ PortInfo = ask(ID ++ "c. Port number info (how we shall choose a port)"
+ "~n Note that we do not allow all variants here! "
+ "~n Edit manually for more variants (range/ranges)."
+ "~n default(d)/system(s)/pos-integer",
+ "4000", fun verify_port_number_info/1),
+ TAddress = {Address, PortInfo},
+ Kind = ask(ID ++ "d. Kind of transport "
+ "(all(a)/request-responder(rr)/trap-sender(ts))",
+ "a", fun verify_transport_kind/1),
+ i("*** We do not ask about the transport options here ***~n"
+ "*** the user must manually edit the config files! ***"),
+ case Kind of
+ all ->
+ {TDomain, TAddress, []};
+ _ when (Kind =:= req_responder) orelse (Kind =:= trap_sender) ->
+ {TDomain, TAddress, Kind, []}
+ end.
+
config_agent_snmp(Dir, Vsns) ->
i("~nAgent snmp config: "
"~n------------------"),
@@ -511,21 +554,25 @@ config_agent_snmp(Dir, Vsns) ->
EngineName, fun verify_engine_id/1),
MMS = ask("3. Max message size?", "484",
fun verify_max_message_size/1),
- AgentUDP = ask("4. The UDP port the agent listens to. "
- "(standard 161)",
- "4000", fun verify_port_number/1),
- Host = host(),
- AgentIP = ask("5. IP address for the agent (only used as id ~n"
- " when sending traps)", Host, fun verify_address/1),
- %% We intentionally skip TDomain...
- %% If the user wish to use IPv6, the user must create an dummy entry here
- %% and then manually edit these entries later.
+ ATransports = config_agent_transports("4"),
+ %% AgentUDP = ask("4. The UDP port the agent listens to. "
+ %% "(standard 161)",
+ %% "4000", fun verify_port_number/1),
+ %% AgentIP = ask("5. IP address for the agent (only used as id ~n"
+ %% " when sending traps)", Host, fun verify_address/1),
+
+ ManagerTDomain = ask("5. Manager Transport Domain"
+ "(UDP IPv4 (u4) or UDP IPv6 (u6))",
+ "u4", fun verify_transport_domain/1),
+ Host = host(ManagerTDomain),
ManagerIP = ask("6. IP address for the manager (only this manager ~n"
" will have access to the agent, traps are sent ~n"
- " to this one)", Host, fun verify_address/1),
- TrapUdp = ask("7. To what UDP port at the manager should traps ~n"
+ " to this one)", Host,
+ fun(A) -> verify_transport_address(ManagerTDomain, A) end),
+ ManagerUdp = ask("7. To what UDP port at the manager should traps ~n"
" be sent (standard 162)?", "5000",
fun verify_port_number/1),
+
SecType = ask("8. Do you want a none- minimum- or semi-secure"
" configuration? ~n"
" Note that if you chose v1 or v2, you won't get any"
@@ -571,8 +618,13 @@ config_agent_snmp(Dir, Vsns) ->
end,
NT
end,
+
+
+
case (catch write_agent_snmp_files(
- Dir, Vsns, ManagerIP, TrapUdp, AgentIP, AgentUDP, SysName,
+ Dir, Vsns,
+ ManagerTDomain, ManagerIP, ManagerUdp,
+ ATransports, SysName,
NotifType, SecType, Passwd, EngineID, MMS)) of
ok ->
i("~n- - - - - - - - - - - - -"),
@@ -679,7 +731,7 @@ config_manager_sys() ->
"(true/false)?",
"false", fun verify_bool/1),
NetIfNoReuse = ask("17. Shall the manager IP address and port "
- "be not reusable (true/false)?",
+ "be *not* reusable (true/false)?",
"false", fun verify_bool/1),
NetIfRecbuf =
case ask("18. Receive buffer size of the manager (in bytes) "
@@ -1113,8 +1165,14 @@ verify_address(A, transportDomainUdpIpv6 = _Domain) ->
do_verify_address(A, inet6).
do_verify_address(A, Family) ->
+ do_verify_address(A, Family, list).
+
+do_verify_address(A, Family, Form)
+ when (Form =:= list) orelse (Form =:= tuple) ->
case (catch snmp_misc:ip(A, Family)) of
- {ok, IP} ->
+ {ok, IP} when (Form =:= tuple) ->
+ {ok, IP};
+ {ok, IP} when (Form =:= list) ->
{ok, tuple_to_list(IP)};
{error, _} ->
{error, "invalid address: " ++ A};
@@ -1348,6 +1406,55 @@ verify_irb_user(TO) ->
end.
+verify_transport_domain("u4") ->
+ {ok, transportDomainUdpIpv4};
+verify_transport_domain("udp4") ->
+ {ok, transportDomainUdpIpv4};
+verify_transport_domain("transportDomainUdpIpv4") ->
+ {ok, transportDomainUdpIpv4};
+verify_transport_domain("u6") ->
+ {ok, transportDomainUdpIpv6};
+verify_transport_domain("udp6") ->
+ {ok, transportDomainUdpIpv6};
+verify_transport_domain("transportDomainUdpIpv6") ->
+ {ok, transportDomainUdpIpv6};
+verify_transport_domain(TS) ->
+ {error, "invalid transport domain: " ++ TS}.
+
+
+verify_transport_address(transportDomainUdpIpv4 = _Domain, A) ->
+ do_verify_address(A, inet, tuple);
+verify_transport_address(transportDomainUdpIpv6 = _Domain, A) ->
+ do_verify_address(A, inet6, tuple).
+
+
+verify_transport_kind("a") ->
+ {ok, all};
+verify_transport_kind("rr") ->
+ {ok, req_responder};
+verify_transport_kind("ts") ->
+ {ok, trap_sender};
+verify_transport_kind(K) ->
+ {error, "invalid transport kind: " ++ K}.
+
+
+verify_port_number_info("d") ->
+ {ok, 0};
+verify_port_number_info("default") ->
+ {ok, 0};
+verify_port_number_info("s") ->
+ {ok, system};
+verify_port_number_info("system") ->
+ {ok, system};
+verify_port_number_info(P) ->
+ case (catch list_to_integer(P)) of
+ N when is_integer(N) andalso (N > 0) ->
+ {ok, N};
+ _ ->
+ {error, "invalid port number: " ++ P}
+ end.
+
+
verify_term_disco_behaviour("discovery") ->
{ok, discovery};
verify_term_disco_behaviour("plain") ->
@@ -1540,17 +1647,29 @@ ask(Q, Default, Verify) when is_list(Q) andalso is_function(Verify) ->
host() ->
+ do_host(inet).
+
+host(transportDomainUdpIpv4) ->
+ do_host(inet);
+host(transportDomainUdpIpv6) ->
+ do_host(inet6).
+
+do_host(Fam) ->
case (catch inet:gethostname()) of
{ok, Name} ->
- case (catch inet:getaddr(Name, inet)) of
+ case (catch inet:getaddr(Name, Fam)) of
{ok, Addr} when is_tuple(Addr) ->
lists:flatten(
io_lib:format("~w.~w.~w.~w", tuple_to_list(Addr)));
- _ ->
- "127.0.0.1"
+ _ when (Fam =:= inet) ->
+ "127.0.0.1";
+ _ when (Fam =:= inet6) ->
+ "::1"
end;
- _ ->
- "127.0.0.1"
+ _ when (Fam =:= inet) ->
+ "127.0.0.1";
+ _ when (Fam =:= inet6) ->
+ "::1"
end.
guess_agent_name() ->
@@ -1616,10 +1735,88 @@ write_agent_snmp_files(
%% ----- Agent config files generator functions -----
%%
+%% This function has no documentation, so for "possible" users
+%% (other then our test suite), we have this spec...
+
+-type agent_pre_transport() :: #{addr := tuple(),
+ kind := req_responder | trap_sender,
+ opts := list()} |
+ #{addr := tuple(),
+ kind := req_responder | trap_sender} |
+ #{addr := tuple()}.
+
+-spec write_agent_snmp_files(Dir, Vsns,
+ TransportDomain, ManagerAddr, AgentPreTransports,
+ SysName,
+ NotifyType, SecType, Passwd, EngineID, MMS) ->
+ ok when
+ Dir :: file:filename(),
+ Vsns :: [snmp:version()],
+ TransportDomain :: snmp:tdomain(),
+ ManagerAddr :: {inet:ip_address(), inet:port_number()},
+ AgentPreTransports :: [agent_pre_transport()],
+ SysName :: string(),
+ NotifyType :: trap | inform,
+ SecType :: none | minimum | {semi, des | aes},
+ Passwd :: list(),
+ EngineID :: snmp:engine_id(),
+ MMS :: snmp:mms();
+
+ (Dir, Vsns,
+ TransportDomain, ManagerAddr, AgentAddr,
+ SysName,
+ NotifyType, SecType, Passwd, EngineID, MMS) ->
+ ok when
+ Dir :: file:filename(),
+ Vsns :: [snmp:version()],
+ TransportDomain :: snmp:tdomain(),
+ ManagerAddr :: {inet:ip_address(), inet:port_number()},
+ AgentAddr :: {inet:ip_address(), inet:port_number()},
+ SysName :: string(),
+ NotifyType :: trap | inform,
+ SecType :: none | minimum | {semi, des | aes},
+ Passwd :: list(),
+ EngineID :: snmp:engine_id(),
+ MMS :: snmp:mms().
+
+write_agent_snmp_files(
+ Dir, Vsns, TransportDomain, ManagerAddr, AgentPreTransports, SysName,
+ NotifType, SecType, Passwd, EngineID, MMS) when is_list(AgentPreTransports) ->
+ F = fun(#{addr := Addr, kind := Kind, opts := Opts})
+ when is_tuple(Addr) andalso
+ is_atom(Kind) andalso
+ is_list(Opts) ->
+ {TransportDomain, Addr, Kind, Opts};
+ (#{addr := Addr, kind := Kind})
+ when is_tuple(Addr) andalso
+ is_atom(Kind) ->
+ {TransportDomain, Addr, Kind, []};
+ (#{addr := Addr})
+ when is_tuple(Addr) ->
+ {TransportDomain, Addr}
+ end,
+ AgentTransports = lists:map(F, AgentPreTransports),
+ write_agent_snmp_conf(Dir, AgentTransports, EngineID, MMS),
+ write_agent_snmp_context_conf(Dir),
+ write_agent_snmp_community_conf(Dir),
+ write_agent_snmp_standard_conf(Dir, SysName),
+ write_agent_snmp_target_addr_conf(Dir, TransportDomain, ManagerAddr, Vsns),
+ write_agent_snmp_target_params_conf(Dir, Vsns),
+ write_agent_snmp_notify_conf(Dir, NotifType),
+ write_agent_snmp_usm_conf(Dir, Vsns, EngineID, SecType, Passwd),
+ write_agent_snmp_vacm_conf(Dir, Vsns, SecType),
+ ok;
write_agent_snmp_files(
Dir, Vsns, Domain, ManagerAddr, AgentAddr, SysName,
- NotifType, SecType, Passwd, EngineID, MMS) ->
- write_agent_snmp_conf(Dir, Domain, AgentAddr, EngineID, MMS),
+ NotifType, SecType, Passwd, EngineID, MMS)
+ when is_list(Dir) andalso
+ is_list(Vsns) andalso
+ is_atom(Domain) andalso
+ is_tuple(ManagerAddr) andalso
+ is_tuple(ManagerAddr) andalso
+ is_list(SysName) andalso
+ is_atom(NotifType) ->
+ write_agent_snmp_conf(Dir, [{Domain, AgentAddr}], EngineID, MMS),
write_agent_snmp_context_conf(Dir),
write_agent_snmp_community_conf(Dir),
write_agent_snmp_standard_conf(Dir, SysName),
@@ -1632,7 +1829,15 @@ write_agent_snmp_files(
write_agent_snmp_files(
Dir, Vsns, ManagerIP, TrapUDP, AgentIP, AgentUDP, SysName,
- NotifType, SecType, Passwd, EngineID, MMS) ->
+ NotifType, SecType, Passwd, EngineID, MMS)
+ when is_list(Dir) andalso
+ is_list(Vsns) andalso
+ is_list(ManagerIP) andalso
+ is_integer(TrapUDP) andalso
+ is_list(AgentIP) andalso
+ is_integer(AgentUDP) andalso
+ is_list(SysName) andalso
+ is_atom(NotifType) ->
Domain = snmp_target_mib:default_domain(),
ManagerAddr = {ManagerIP, TrapUDP},
write_agent_snmp_conf(Dir, AgentIP, AgentUDP, EngineID, MMS),
@@ -1644,6 +1849,30 @@ write_agent_snmp_files(
write_agent_snmp_notify_conf(Dir, NotifType),
write_agent_snmp_usm_conf(Dir, Vsns, EngineID, SecType, Passwd),
write_agent_snmp_vacm_conf(Dir, Vsns, SecType),
+ ok;
+write_agent_snmp_files(
+ Dir, Vsns,
+ ManagerTDomain, ManagerIP, ManagerTrapUDP,
+ AgentTransports, SysName,
+ NotifType, SecType, Passwd, EngineID, MMS)
+ when is_list(Dir) andalso
+ is_list(Vsns) andalso
+ is_atom(ManagerTDomain) andalso
+ is_tuple(ManagerIP) andalso
+ is_integer(ManagerTrapUDP) andalso
+ is_list(AgentTransports) andalso
+ is_list(SysName) andalso
+ is_atom(NotifType) ->
+ ManagerAddr = {ManagerIP, ManagerTrapUDP},
+ write_agent_snmp_conf(Dir, AgentTransports, EngineID, MMS),
+ write_agent_snmp_context_conf(Dir),
+ write_agent_snmp_community_conf(Dir),
+ write_agent_snmp_standard_conf(Dir, SysName),
+ write_agent_snmp_target_addr_conf(Dir, ManagerTDomain, ManagerAddr, Vsns),
+ write_agent_snmp_target_params_conf(Dir, Vsns),
+ write_agent_snmp_notify_conf(Dir, NotifType),
+ write_agent_snmp_usm_conf(Dir, Vsns, EngineID, SecType, Passwd),
+ write_agent_snmp_vacm_conf(Dir, Vsns, SecType),
ok.
@@ -2105,6 +2334,22 @@ update_agent_vacm_config(Dir, Conf) ->
%% ----- Manager config files generator functions -----
%%
+write_manager_snmp_files(Dir, Transports, MMS, EngineID) ->
+ write_manager_snmp_files(Dir, Transports, MMS, EngineID,
+ [], [], []).
+
+write_manager_snmp_files(Dir, IP, Port, MMS, EngineID) ->
+ write_manager_snmp_files(Dir, IP, Port, MMS, EngineID,
+ [], [], []).
+
+write_manager_snmp_files(Dir, Transports, MMS, EngineID,
+ Users, Agents, Usms) ->
+ write_manager_snmp_conf(Dir, Transports, MMS, EngineID),
+ write_manager_snmp_users_conf(Dir, Users),
+ write_manager_snmp_agents_conf(Dir, Agents),
+ write_manager_snmp_usm_conf(Dir, Usms),
+ ok.
+
write_manager_snmp_files(Dir, IP, Port, MMS, EngineID,
Users, Agents, Usms) ->
write_manager_snmp_conf(Dir, IP, Port, MMS, EngineID),
@@ -2130,7 +2375,7 @@ write_manager_snmp_conf(Dir, Transports, MMS, EngineID) ->
"%%\n\n",
Hdr = header() ++ Comment,
Conf =
- [{transports, Transports},
+ [{transports, Transports},
{engine_id, EngineID},
{max_message_size, MMS}],
write_manager_config(Dir, Hdr, Conf).
diff --git a/lib/snmp/src/misc/snmp_verbosity.erl b/lib/snmp/src/misc/snmp_verbosity.erl
index 9bc3e2762f..0572ebea03 100644
--- a/lib/snmp/src/misc/snmp_verbosity.erl
+++ b/lib/snmp/src/misc/snmp_verbosity.erl
@@ -100,7 +100,9 @@ image_of_verbosity(_) -> "".
%% ShortName
image_of_sname(ma) -> "MASTER-AGENT";
-image_of_sname(maw) -> io_lib:format("MASTER-AGENT-worker(~p)",[self()]);
+image_of_sname(mamw) -> io_lib:format("MASTER-AGENT-main_worker(~p)",[self()]);
+image_of_sname(masw) -> io_lib:format("MASTER-AGENT-set_worker(~p)",[self()]);
+image_of_sname(manw) -> io_lib:format("MASTER-AGENT-notif_worker(~p)",[self()]);
image_of_sname(madis) -> io_lib:format("MASTER-AGENT-discovery_inform_sender(~p)",
[self()]);
image_of_sname(mais) -> io_lib:format("MASTER-AGENT-inform_sender(~p)",
diff --git a/lib/snmp/test/modules.mk b/lib/snmp/test/modules.mk
index ba0aea21dd..66cfa07f3b 100644
--- a/lib/snmp/test/modules.mk
+++ b/lib/snmp/test/modules.mk
@@ -38,6 +38,7 @@ TEST_UTIL_MODULES = \
snmp_test_lib \
snmp_agent_test_lib \
snmp_agent_test_get \
+ snmp_otp16649_user \
snmp_manager_user \
snmp_manager_user_old \
snmp_manager_user_test_lib \
diff --git a/lib/snmp/test/snmp_agent_SUITE.erl b/lib/snmp/test/snmp_agent_SUITE.erl
index c676bc487e..3d8170cada 100644
--- a/lib/snmp/test/snmp_agent_SUITE.erl
+++ b/lib/snmp/test/snmp_agent_SUITE.erl
@@ -204,7 +204,7 @@
v3_sha_auth/1,
v3_des_priv/1,
- %% all_tcs - test_multi_threaded
+ %% all_tcs - test_multi_threaded, test_multi_threaded_ext
multi_threaded/1,
mt_trap/1,
@@ -303,7 +303,14 @@
%% tickets2
otp8395/1,
- otp9884/1
+ otp9884/1,
+ otp16649_1/1,
+ otp16649_2/1,
+ otp16649_3/1,
+ otp16649_4/1,
+ otp16649_5/1,
+ otp16649_6/1,
+ otp16649_7/1
]).
%% Internal exports
@@ -319,7 +326,7 @@
db_notify_client_test/0,
notify/2,
multi_threaded_test/0,
- mt_trap_test/1,
+ mt_trap_test/2,
types_v2_test/0,
implied_test/1,
sparse_table_test/0,
@@ -420,6 +427,9 @@
mnesia_start/0,
mnesia_stop/0,
start_standalone_agent/1,
+ stop_standalone_agent/1,
+ start_standalone_manager/1,
+ stop_standalone_manager/1,
do_info/1
]).
@@ -441,6 +451,9 @@
-define(sa, [1,3,6,1,4,1,193,2]).
-define(system, [1,3,6,1,2,1,1]).
-define(snmp, [1,3,6,1,2,1,11]).
+-define(sysDescr_instance, [1,3,6,1,2,1,1,1,0]).
+-define(sysObjectID_instance, [1,3,6,1,2,1,1,2,0]).
+-define(sysUpTime_instance, [1,3,6,1,2,1,1,3,0]).
-define(snmpTraps, [1,3,6,1,6,3,1,1,5]).
-define(ericsson, [1,3,6,1,4,1,193]).
-define(testTrap, [1,3,6,1,2,1,15,0]).
@@ -456,6 +469,11 @@
-define(TRAP_UDP, 5000).
+-define(MGR_PORT, 5000).
+-define(MGR_MMS, 1024).
+-define(MGR_ENGINE_ID, "mgrEngine").
+
+
-define(tooBigStr, "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").
-define(str(X), snmp_pdus:bits_to_str(X)).
@@ -535,6 +553,7 @@ groups() ->
{test_v3, [], v3_cases()},
{test_v3_ipv6, [], v3_cases_ipv6()},
{test_multi_threaded, [], mt_cases()},
+ {test_multi_threaded_ext, [], mt_cases()},
{multiple_reqs, [], mul_cases()},
{multiple_reqs_2, [], mul_cases_2()},
{multiple_reqs_3, [], mul_cases_3()},
@@ -552,7 +571,10 @@ groups() ->
{tickets2, [], tickets2_cases()},
{otp4394, [], [otp_4394]},
{otp7157, [], [otp_7157]},
- {otp16092, [], otp16092_cases()}
+ {otp16092, [], otp16092_cases()},
+ {otp16649, [], otp16649_cases()},
+ {otp16649_ipv4, [], otp16649_gen_cases()},
+ {otp16649_ipv6, [], otp16649_gen_cases()}
].
@@ -568,6 +590,7 @@ all_cases() ->
{group, test_v1_v2_ipv6},
{group, test_v3_ipv6},
{group, test_multi_threaded},
+ {group, test_multi_threaded_ext},
{group, mib_storage},
{group, tickets1}
].
@@ -654,7 +677,9 @@ init_per_group(multiple_reqs_2 = GroupName, Config) ->
init_per_group(multiple_reqs_3 = GroupName, Config) ->
init_mul(snmp_test_lib:init_group_top_dir(GroupName, Config));
init_per_group(test_multi_threaded = GroupName, Config) ->
- init_mt(snmp_test_lib:init_group_top_dir(GroupName, Config));
+ init_mt(snmp_test_lib:init_group_top_dir(GroupName, Config), true);
+init_per_group(test_multi_threaded_ext = GroupName, Config) ->
+ init_mt(snmp_test_lib:init_group_top_dir(GroupName, Config), extended);
init_per_group(test_v3 = GroupName, Config) ->
case snmp_test_lib:crypto_start() of
ok ->
@@ -714,6 +739,45 @@ init_per_group(mib_storage_dets = GroupName, Config) ->
init_mib_storage_dets(snmp_test_lib:init_group_top_dir(GroupName, Config));
init_per_group(mib_storage_ets = GroupName, Config) ->
init_mib_storage_ets(snmp_test_lib:init_group_top_dir(GroupName, Config));
+init_per_group(otp16649_ipv4 = GroupName, Config) ->
+ Config2 = [{ip, ?LOCALHOST(inet)},
+ {ipfamily, inet},
+ {tdomain, transportDomainUdpIpv4} |
+ lists:keydelete(ip, 1, Config)],
+ snmp_test_lib:init_group_top_dir(GroupName, Config2);
+init_per_group(otp16649_ipv6 = GroupName, Config) ->
+ init_per_group_ipv6(GroupName,
+ [{tdomain, transportDomainUdpIpv6} | Config],
+ fun(C) -> C end);
+ %% SupportsIPv6 =
+ %% case ?HAS_SUPPORT_IPV6() of
+ %% true ->
+ %% case os:type() of
+ %% {unix, netbsd} ->
+ %% {false, "Host *may* not *properly* support IPV6"};
+ %% {unix, darwin} ->
+ %% case os:version() of
+ %% V > {9, 8, 0} ->
+ %% true;
+ %% _ ->
+ %% {false, "Host *may* not *properly* support IPV6"};
+ %% end;
+ %% _ ->
+ %% true
+ %% end;
+ %% false ->
+ %% {false, "Host does not support IPv6"}
+ %% end,
+ %% case SupportsIPv6 of
+ %% true ->
+ %% Config2 = [{ip, ?LOCALHOST(inet6)},
+ %% {ipfamily, inet6},
+ %% {tdomain, transportDomainUdpIpv6} |
+ %% lists:keydelete(ip, 1, Config)],
+ %% snmp_test_lib:init_group_top_dir(GroupName, Config2);
+ %% {false, SkipReason} ->
+ %% {skip, SkipReason}
+ %% end;
init_per_group(GroupName, Config) ->
snmp_test_lib:init_group_top_dir(GroupName, Config).
@@ -767,49 +831,51 @@ end_per_group(v2_inform, Config) ->
end_per_group(v3_inform, Config) ->
finish_v3_inform(Config);
end_per_group(multiple_reqs, Config) ->
- finish_mul(Config);
+ finish_mul(Config);
end_per_group(multiple_reqs_2, Config) ->
finish_mul(Config);
end_per_group(multiple_reqs_3, Config) ->
finish_mul(Config);
end_per_group(test_multi_threaded, Config) ->
- finish_mt(Config);
+ finish_mt(Config);
+end_per_group(test_multi_threaded_ext, Config) ->
+ finish_mt(Config);
end_per_group(test_v3_ipv6, Config) ->
- finish_v3(Config);
+ finish_v3(Config);
end_per_group(test_v1_v2_ipv6, Config) ->
- finish_v1_v2(Config);
+ finish_v1_v2(Config);
end_per_group(test_v2_ipv6, Config) ->
- finish_v2(Config);
+ finish_v2(Config);
end_per_group(test_v1_ipv6, Config) ->
- finish_v1(Config);
+ finish_v1(Config);
end_per_group(test_v3, Config) ->
- finish_v3(Config);
+ finish_v3(Config);
end_per_group(test_v1_v2, Config) ->
- finish_v1_v2(Config);
+ finish_v1_v2(Config);
end_per_group(test_v2, Config) ->
- finish_v2(Config);
+ finish_v2(Config);
end_per_group(test_v1, Config) ->
- finish_v1(Config);
+ finish_v1(Config);
end_per_group(misc, Config) ->
- finish_misc(Config);
+ finish_misc(Config);
end_per_group(mib_storage_varm_mnesia, Config) ->
- finish_varm_mib_storage_mnesia(Config);
+ finish_varm_mib_storage_mnesia(Config);
end_per_group(mib_storage_varm_dets, Config) ->
- finish_varm_mib_storage_dets(Config);
+ finish_varm_mib_storage_dets(Config);
end_per_group(mib_storage_size_check_mnesia, Config) ->
- finish_size_check_msm(Config);
+ finish_size_check_msm(Config);
end_per_group(mib_storage_size_check_dets, Config) ->
- finish_size_check_msd(Config);
+ finish_size_check_msd(Config);
end_per_group(mib_storage_size_check_ets, Config) ->
- finish_size_check_mse(Config);
+ finish_size_check_mse(Config);
end_per_group(mib_storage_mnesia, Config) ->
- finish_mib_storage_mnesia(Config);
+ finish_mib_storage_mnesia(Config);
end_per_group(mib_storage_dets, Config) ->
- finish_mib_storage_dets(Config);
+ finish_mib_storage_dets(Config);
end_per_group(mib_storage_ets, Config) ->
- finish_mib_storage_ets(Config);
+ finish_mib_storage_ets(Config);
end_per_group(_GroupName, Config) ->
- Config.
+ Config.
@@ -858,6 +924,41 @@ init_per_testcase1(otp9884 = Case, Config) when is_list(Config) ->
"~n Case: ~p"
"~n Config: ~p", [Case, Config]),
otp9884({init, init_per_testcase2(Case, Config)});
+init_per_testcase1(otp16649_1 = Case, Config) when is_list(Config) ->
+ ?DBG("init_per_testcase1 -> entry with"
+ "~n Case: ~p"
+ "~n Config: ~p", [Case, Config]),
+ otp16649_1_init(init_per_testcase2(Case, Config));
+init_per_testcase1(otp16649_2 = Case, Config) when is_list(Config) ->
+ ?DBG("init_per_testcase1 -> entry with"
+ "~n Case: ~p"
+ "~n Config: ~p", [Case, Config]),
+ otp16649_2_init(init_per_testcase2(Case, Config));
+init_per_testcase1(otp16649_3 = Case, Config) when is_list(Config) ->
+ ?DBG("init_per_testcase1 -> entry with"
+ "~n Case: ~p"
+ "~n Config: ~p", [Case, Config]),
+ otp16649_3_init(init_per_testcase2(Case, Config));
+init_per_testcase1(otp16649_4 = Case, Config) when is_list(Config) ->
+ ?DBG("init_per_testcase1 -> entry with"
+ "~n Case: ~p"
+ "~n Config: ~p", [Case, Config]),
+ otp16649_4_init(init_per_testcase2(Case, Config));
+init_per_testcase1(otp16649_5 = Case, Config) when is_list(Config) ->
+ ?DBG("init_per_testcase1 -> entry with"
+ "~n Case: ~p"
+ "~n Config: ~p", [Case, Config]),
+ otp16649_5_init(init_per_testcase2(Case, Config));
+init_per_testcase1(otp16649_6 = Case, Config) when is_list(Config) ->
+ ?DBG("init_per_testcase1 -> entry with"
+ "~n Case: ~p"
+ "~n Config: ~p", [Case, Config]),
+ otp16649_6_init(init_per_testcase2(Case, Config));
+init_per_testcase1(otp16649_7 = Case, Config) when is_list(Config) ->
+ ?DBG("init_per_testcase1 -> entry with"
+ "~n Case: ~p"
+ "~n Config: ~p", [Case, Config]),
+ otp16649_7_init(init_per_testcase2(Case, Config));
init_per_testcase1(otp_7157 = _Case, Config) when is_list(Config) ->
?DBG("init_per_testcase1 -> entry with"
"~n Case: ~p"
@@ -976,6 +1077,20 @@ end_per_testcase1(otp8395, Config) when is_list(Config) ->
otp8395({fin, Config});
end_per_testcase1(otp9884, Config) when is_list(Config) ->
otp9884({fin, Config});
+end_per_testcase1(otp16649_1, Config) when is_list(Config) ->
+ otp16649_1_fin(Config);
+end_per_testcase1(otp16649_2, Config) when is_list(Config) ->
+ otp16649_2_fin(Config);
+end_per_testcase1(otp16649_3, Config) when is_list(Config) ->
+ otp16649_3_fin(Config);
+end_per_testcase1(otp16649_4, Config) when is_list(Config) ->
+ otp16649_4_fin(Config);
+end_per_testcase1(otp16649_5, Config) when is_list(Config) ->
+ otp16649_5_fin(Config);
+end_per_testcase1(otp16649_6, Config) when is_list(Config) ->
+ otp16649_6_fin(Config);
+end_per_testcase1(otp16649_7, Config) when is_list(Config) ->
+ otp16649_7_fin(Config);
end_per_testcase1(_Case, Config) when is_list(Config) ->
?DBG("end_per_testcase1 -> entry with"
"~n Case: ~p"
@@ -1044,8 +1159,8 @@ start_v3_agent(Config, Opts) ->
start_bilingual_agent(Config) ->
?ALIB:start_bilingual_agent(Config).
-start_multi_threaded_agent(Config) when is_list(Config) ->
- ?ALIB:start_mt_agent(Config).
+start_multi_threaded_agent(Config, MT) when is_list(Config) ->
+ [{multi_threaded, MT} | ?ALIB:start_mt_agent(Config, MT)].
stop_agent(Config) ->
?ALIB:stop_agent(Config).
@@ -2122,7 +2237,7 @@ mt_cases() ->
mt_trap
].
-init_mt(Config) when is_list(Config) ->
+init_mt(Config, MT) when is_list(Config) ->
SaNode = ?config(snmp_sa, Config),
create_tables(SaNode),
AgentConfDir = ?config(agent_conf_dir, Config),
@@ -2130,7 +2245,7 @@ init_mt(Config) when is_list(Config) ->
Ip = ?config(ip, Config),
?line ok = config([v2], MgrDir, AgentConfDir,
tuple_to_list(Ip), tuple_to_list(Ip)),
- [{vsn, v2} | start_multi_threaded_agent(Config)].
+ [{vsn, v2} | start_multi_threaded_agent(Config, MT)].
finish_mt(Config) when is_list(Config) ->
delete_tables(),
@@ -2295,10 +2410,11 @@ mt_trap(Config) when is_list(Config) ->
?P(mt_trap),
init_case(Config),
MA = whereis(snmp_master_agent),
+ MT = ?config(multi_threaded, Config),
?line load_master("Test1"),
?line load_master("TestTrapv2"),
- try_test(mt_trap_test, [MA]),
+ try_test(mt_trap_test, [MA, MT]),
?line unload_master("TestTrapv2"),
?line unload_master("Test1"),
ok.
@@ -3964,37 +4080,51 @@ multi_threaded_test() ->
?line ?expect1([{[sysLocation,0], "kalle"}]).
%% Req. Test1, TestTrapv2
-mt_trap_test(MA) ->
- ?NPRINT("Testing trap-sending with multi threaded agent..."),
- ?DBG("mt_trap_test(01) -> issue testTrapv22 (standard trap)", []),
+mt_trap_test(MA, MT) ->
+ ?NPRINT("Testing trap-sending with multi threaded (~w) agent...", [MT]),
+ ?IPRINT("mt_trap_test(01) -> issue testTrapv22 (standard trap)", []),
snmpa:send_trap(MA, testTrapv22, "standard trap"),
- ?DBG("mt_trap_test(02) -> await v2trap", []),
+ ?IPRINT("mt_trap_test(02) -> await v2trap", []),
?line ?expect2(v2trap, [{[sysUpTime, 0], any},
{[snmpTrapOID, 0], ?system ++ [0,1]}]),
- ?DBG("mt_trap_test(03) -> issue mtTrap (standard trap)", []),
+ %% multi-threaded = true
+ %% This will *lock* the 'main thread' of a multi-threaded agent,
+ %% the worker state will be 'busy'. Therefor when a new request
+ %% arrives a new *temporary* worker will be spawned.
+ ?IPRINT("mt_trap_test(03) -> issue mtTrap (standard trap)", []),
snmpa:send_trap(MA, mtTrap, "standard trap"),
Pid = get_multi_pid(),
- ?DBG("mt_trap_test(04) -> multi pid: ~p. Now request sysUpTime...", [Pid]),
+ ?IPRINT("mt_trap_test(04) -> multi pid: ~p. Now request sysUpTime...", [Pid]),
g([[sysUpTime,0]]),
- ?DBG("mt_trap_test(06) -> await sysUpTime", []),
+ ?IPRINT("mt_trap_test(06) -> await sysUpTime", []),
?line ?expect1([{[sysUpTime,0], any}]),
- ?DBG("mt_trap_test(07) -> issue testTrapv22 (standard trap)", []),
- snmpa:send_trap(MA, testTrapv22, "standard trap"),
- ?DBG("mt_trap_test(08) -> await v2trap", []),
- ?line ?expect2(v2trap,
- [{[sysUpTime, 0], any},
- {[snmpTrapOID, 0], ?system ++ [0,1]}]),
- ?DBG("mt_trap_test(09) -> send continue to multi-pid", []),
+ %% This will *only* work if multi-threaded is 'true', not 'extended'
+ %% since in the latter case all notifications are serialized through
+ %% a dedicated worker, which is now locked (see above).
+
+ if
+ (MT =:= true) ->
+ ?IPRINT("mt_trap_test(07) -> issue testTrapv22 (standard trap)", []),
+ snmpa:send_trap(MA, testTrapv22, "standard trap"),
+ ?IPRINT("mt_trap_test(08) -> await v2trap", []),
+ ?line ?expect2(v2trap,
+ [{[sysUpTime, 0], any},
+ {[snmpTrapOID, 0], ?system ++ [0,1]}]);
+ true ->
+ ok
+ end,
+
+ ?IPRINT("mt_trap_test(09) -> send continue to multi-pid", []),
Pid ! continue,
- ?DBG("mt_trap_test(10) -> await v2trap", []),
+ ?IPRINT("mt_trap_test(10) -> await v2trap", []),
?line ?expect2(v2trap, [{[sysUpTime, 0], any},
{[snmpTrapOID, 0], ?testTrap ++ [2]},
{[multiStr,0], "ok"}]),
- ?DBG("mt_trap_test(11) -> done", []),
+ ?IPRINT("mt_trap_test(11) -> done", []),
ok.
@@ -4399,7 +4529,7 @@ sa_mib() ->
ok.
ma_trap1(MA) ->
- ok = snmpa:send_trap(MA, testTrap2, "standard trap"),
+ ok = snmpa:send_trap(MA, testTrap2, "standard trap"),
?line ?expect5(trap, [system], 6, 1, [{[system, [4,0]],
"{mbj,eklas}@erlang.ericsson.se"}]),
ok = snmpa:send_trap(MA, testTrap1, "standard trap"),
@@ -4567,8 +4697,11 @@ ma_v2_inform1(MA) ->
after
20000 ->
?EPRINT("ma_v2_inform1 -> "
- "timeout awaiting snmp_notification [~p]",
- [Tag03]),
+ "timeout awaiting snmp_notification [~p]: "
+ "~n Message Queue: "
+ "~n ~p",
+ [Tag03,
+ process_info(self(), messages)]),
{error, snmp_notification_timeout}
end
end,
@@ -4606,8 +4739,11 @@ ma_v2_inform1(MA) ->
after
240000 ->
?EPRINT("ma_v2_inform1 -> "
- "timeout awaiting snmp_notification [~p]",
- [Tag07]),
+ "timeout awaiting snmp_notification [~p]: "
+ "~n Message Queue: "
+ "~n ~p",
+ [Tag07,
+ process_info(self(), messages)]),
{error, snmp_notification_timeout}
end
end,
@@ -4657,28 +4793,35 @@ ma_v2_inform2(MA) ->
%% Await callback(s)
CmdAwaitDeliveryCallback =
fun(Kind, Ref, Tag) ->
- ?IPRINT("CmdAwaitDeliveryCallback -> entry with"
+ ?IPRINT("ma_v2_inform2:"
+ "CmdAwaitDeliveryCallback -> entry with"
"~n Kind: ~p"
"~n Ref: ~p"
"~n Tag: ~p", [Kind, Ref, Tag]),
receive
{Kind, Ref, ok} ->
- ?IPRINT("CmdAwaitDeliveryCallback(~p,~p) -> "
+ ?IPRINT("ma_v2_inform2:"
+ "CmdAwaitDeliveryCallback(~p,~p) -> "
"received expected result: ok"
"~n", [Tag, Ref]),
ok;
{Kind, Ref, Error} ->
- ?IPRINT("CmdAwaitDeliveryCallback(~p,~p) -> "
+ ?IPRINT("ma_v2_inform2:"
+ "CmdAwaitDeliveryCallback(~p,~p) -> "
"received unexpected result: "
"~n Error: ~p"
"~n", [Tag, Ref, Error]),
{error, {unexpected_response, Error}}
after
240000 ->
- ?EPRINT("ma_v2_inform2 -> "
+ ?EPRINT("ma_v2_inform2:"
+ "CmdAwaitDeliveryCallback(~p,~p) -> "
"timeout awaiting got_response for "
- "snmp_notification [~p]",
- [Tag]),
+ "snmp_notification [~p]: "
+ "~n Message Queue: "
+ "~n ~p",
+ [Tag, Ref, Tag,
+ process_info(self(), messages)]),
{error, snmp_notification_timeout}
end
end,
@@ -4729,7 +4872,8 @@ ma_v2_inform3(MA) ->
CmdExpectInform =
fun(_No, Response) ->
- ?DBG("CmdExpectInform -> ~p: ~n~p", [_No, Response]),
+ ?DBG("ma_v2_inform3:CmdExpectInform -> ~p: "
+ "~n ~p", [_No, Response]),
?expect2({inform, Response},
[{[sysUpTime, 0], any},
{[snmpTrapOID, 0], ?system ++ [0,1]}])
@@ -4739,7 +4883,7 @@ ma_v2_inform3(MA) ->
fun(ok) ->
ok;
({ok, _Val}) ->
- ?DBG("CmdExp -> Val: ~p", [_Val]),
+ ?DBG("ma_v2_inform3:CmdExp -> Val: ~p", [_Val]),
ok;
({error, Id, Extra}) ->
{error, {unexpected, Id, Extra}};
@@ -4750,28 +4894,35 @@ ma_v2_inform3(MA) ->
%% Await callback(s)
CmdAwaitDeliveryCallback =
fun(Kind, Ref, Tag) ->
- ?IPRINT("CmdAwaitDeliveryCallback -> entry with"
+ ?IPRINT("ma_v2_inform3:"
+ "CmdAwaitDeliveryCallback -> entry with"
"~n Kind: ~p"
"~n Ref: ~p"
"~n Tag: ~p", [Kind, Ref, Tag]),
receive
{Kind, Ref, ok} ->
- ?IPRINT("CmdAwaitDeliveryCallback(~p,~p) -> "
+ ?IPRINT("ma_v2_inform3:"
+ "CmdAwaitDeliveryCallback(~p,~p) -> "
"received expected result: ok"
"~n", [Tag, Ref]),
ok;
{Kind, Ref, Error} ->
- ?IPRINT("CmdAwaitDeliveryCallback(~p,~p) -> "
+ ?IPRINT("ma_v2_inform3:"
+ "CmdAwaitDeliveryCallback(~p,~p) -> "
"received unexpected result: "
"~n Error: ~p"
"~n", [Tag, Ref, Error]),
{error, {unexpected_response, Error}}
after
240000 ->
- ?EPRINT("ma_v2_inform3 -> "
+ ?EPRINT("ma_v2_inform3:"
+ "CmdAwaitDeliveryCallback(~p,~p) -> "
"timeout awaiting got_response for "
- "snmp_notification [~p]",
- [Tag]),
+ "snmp_notification [~p]: "
+ "~n Message Queue: "
+ "~n ~p",
+ [Kind, Ref, Tag,
+ process_info(self(), messages)]),
{error, snmp_notification_timeout}
end
end,
@@ -7032,12 +7183,12 @@ otp16092_try_start_and_stop_agent(Node, Opts, Expected) ->
?IPRINT("try start snmp (agent) supervisor (on ~p) - expect ~p",
[Node, Expected]),
case start_standalone_agent(Node, Opts) of
- Pid when is_pid(Pid) andalso (Expected =:= success) ->
+ {ok, Pid} when is_pid(Pid) andalso (Expected =:= success) ->
?IPRINT("Expected success starting snmp (agent) supervisor"),
?SLEEP(1000),
stop_standalone_agent(Pid),
ok;
- Pid when is_pid(Pid) andalso (Expected =:= failure) ->
+ {ok, Pid} when is_pid(Pid) andalso (Expected =:= failure) ->
?EPRINT("Unexpected success starting snmp (agent) supervisor: (~p)",
[Pid]),
?SLEEP(1000),
@@ -7094,7 +7245,6 @@ otp16092_try_start_and_stop_agent(Node, Opts, Expected) ->
end,
ok.
-
%%-----------------------------------------------------------------
@@ -7105,7 +7255,25 @@ otp16092_try_start_and_stop_agent(Node, Opts, Expected) ->
tickets2_cases() ->
[
otp8395,
- otp9884
+ otp9884,
+ {group, otp16649}
+ ].
+
+otp16649_cases() ->
+ [
+ {group, otp16649_ipv4},
+ {group, otp16649_ipv6}
+ ].
+
+otp16649_gen_cases() ->
+ [
+ otp16649_1,
+ otp16649_2,
+ otp16649_3,
+ otp16649_4,
+ otp16649_5,
+ otp16649_6,
+ otp16649_7
].
@@ -7113,9 +7281,9 @@ otp8395({init, Config}) when is_list(Config) ->
?DBG("otp8395(init) -> entry with"
"~n Config: ~p", [Config]),
- %% --
+ %% --
%% Start nodes
- %%
+ %%
{ok, AgentNode} = start_node(agent),
{ok, ManagerNode} = start_node(manager),
@@ -7371,6 +7539,517 @@ otp9884_await_backup_completion(First, Second) ->
%%-----------------------------------------------------------------
+otp16649_1_init(Config) ->
+ AgentPreTransports = [{4000, req_responder},
+ {4001, trap_sender}],
+ otp16649_init(1, AgentPreTransports, Config).
+
+otp16649_1_fin(Config) ->
+ otp16649_fin(1, Config).
+
+otp16649_1(doc) ->
+ "OTP-16649 - Multiple transports.";
+otp16649_1(Config) when is_list(Config) ->
+ otp16649(1, Config).
+
+
+otp16649_2_init(Config) ->
+ AgentPreTransports = [{4000, req_responder},
+ {system, trap_sender}],
+ otp16649_init(2, AgentPreTransports, Config).
+
+otp16649_2_fin(Config) ->
+ otp16649_fin(2, Config).
+
+otp16649_2(doc) ->
+ "OTP-16649 - Multiple transports.";
+otp16649_2(Config) when is_list(Config) ->
+ otp16649(2, Config).
+
+
+otp16649_3_init(Config) ->
+ AgentPreTransports = [{4000, req_responder},
+ {0, trap_sender}],
+ otp16649_init(3, AgentPreTransports, Config).
+
+otp16649_3_fin(Config) ->
+ otp16649_fin(3, Config).
+
+otp16649_3(doc) ->
+ "OTP-16649 - Multiple transports.";
+otp16649_3(Config) when is_list(Config) ->
+ otp16649(3, Config).
+
+
+otp16649_4_init(Config) ->
+ AgentPreTransports = [{4000, req_responder},
+ {{4000,4010}, trap_sender, [{no_reuse, true}]}],
+ otp16649_init(4, AgentPreTransports, Config).
+
+otp16649_4_fin(Config) ->
+ otp16649_fin(4, Config).
+
+otp16649_4(doc) ->
+ "OTP-16649 - Multiple transports.";
+otp16649_4(Config) when is_list(Config) ->
+ otp16649(4, Config).
+
+
+otp16649_5_init(Config) ->
+ AgentPreTransports = [{4000, req_responder},
+ {[{4000,4010}], trap_sender, [{no_reuse, true}]}],
+ otp16649_init(5, AgentPreTransports, Config).
+
+otp16649_5_fin(Config) ->
+ otp16649_fin(5, Config).
+
+otp16649_5(doc) ->
+ "OTP-16649 - Multiple transports.";
+otp16649_5(Config) when is_list(Config) ->
+ otp16649(5, Config).
+
+
+otp16649_6_init(Config) ->
+ AgentPreTransports = [{4000, req_responder},
+ {[4000,4001,4002], trap_sender, [{no_reuse, true}]}],
+ otp16649_init(6, AgentPreTransports, Config).
+
+otp16649_6_fin(Config) ->
+ otp16649_fin(5, Config).
+
+otp16649_6(doc) ->
+ "OTP-16649 - Multiple transports.";
+otp16649_6(Config) when is_list(Config) ->
+ otp16649(6, Config).
+
+
+otp16649_7_init(Config) ->
+ AgentPreTransports = [{4000, req_responder},
+ {[4000,{5100,5110}], trap_sender,
+ [{no_reuse, true}]}],
+ otp16649_init(7, AgentPreTransports, Config).
+
+otp16649_7_fin(Config) ->
+ otp16649_fin(7, Config).
+
+otp16649_7(doc) ->
+ "OTP-16649 - Multiple transports.";
+otp16649_7(Config) when is_list(Config) ->
+ otp16649(7, Config).
+
+
+otp16649(N, Config) ->
+ ?IPRINT("otp16649 -> entry with"
+ "~n N: ~w"
+ "~n Config: ~p", [N, Config]),
+
+ AgentNode = ?config(agent_node, Config),
+ ManagerNode = ?config(manager_node, Config),
+
+ ?line AInfo = rpc:call(AgentNode, snmpa, info, []),
+
+ ?IPRINT("Agent Info: "
+ "~n ~p", [AInfo]),
+
+ {value, {_, AgentRawTransports}} =
+ lists:keysearch(agent_raw_transports, 1, Config),
+ {value, {_, NetIF}} =
+ lists:keysearch(net_if, 1, AInfo),
+ {value, {_, TIs}} =
+ lists:keysearch(transport_info, 1, NetIF),
+
+ if (length(AgentRawTransports) =:= length(TIs)) ->
+ ok;
+ true ->
+ ?IPRINT("Invalid transports: "
+ "~n Number of raw transports: ~w"
+ "~n Number of transports: ~w",
+ [length(AgentRawTransports), length(TIs)]),
+ exit({invalid_num_transports,
+ length(AgentRawTransports), length(TIs)})
+ end,
+
+ ?IPRINT("validate transports"),
+ otp16649_validate_transports(AgentRawTransports, TIs),
+
+ ?IPRINT("which req-responder port-no"),
+ AgentReqPortNo = otp16649_which_req_port_no(TIs),
+
+ ?IPRINT("which trap-sender port-no"),
+ AgentTrapPortNo = otp16649_which_trap_port_no(TIs),
+
+ ?IPRINT("(mgr) register user"),
+ ?line ok = otp16649_mgr_reg_user(ManagerNode),
+
+ ?IPRINT("(mgr) register agent"),
+ TargetBase = "otp16649-agent-",
+ ReqTarget = TargetBase ++ "req",
+ TrapTarget = TargetBase ++ "trap",
+
+ ?line ok = otp16649_mgr_reg_agent(ManagerNode,
+ ?config(ipfamily, Config),
+ ?config(tdomain, Config),
+ ReqTarget, AgentReqPortNo),
+ ?line ok = otp16649_mgr_reg_agent(ManagerNode,
+ ?config(ipfamily, Config),
+ ?config(tdomain, Config),
+ TrapTarget, AgentTrapPortNo),
+
+ ?IPRINT("(mgr) simple (sync) get request"),
+ Oids = [?sysObjectID_instance, ?sysDescr_instance, ?sysUpTime_instance],
+ ?line ok = case otp16649_mgr_get_req(ManagerNode, Oids) of
+ {ok, {noError, 0, ReplyOids}, _} ->
+ ?IPRINT("(mgr) simple (sync) successful reply: "
+ "~n ~p", [ReplyOids]),
+ ok;
+ {ok, InvalidReply, _} ->
+ ?IPRINT("(mgr) simple (sync) invalid reply: "
+ "~n ~p", [InvalidReply]),
+ ok;
+ {error, Reason} ->
+ ?IPRINT("(mgr) simple (sync) error: "
+ "~n ~p", [Reason]),
+ error
+ end,
+
+ ?IPRINT("load TestTrap..."),
+ MibDir = ?config(mib_dir, Config),
+ ?line ok = otp16649_agent_load_mib(AgentNode, MibDir, "TestTrap"),
+
+ ?IPRINT("(agent) send trap (testTrap2)"),
+ ?line ok = otp16649_agent_send_trap(AgentNode, testTrap2),
+
+ TDomain = ?config(tdomain, Config),
+
+ receive
+ {handle_trap, From_v1, TrapTarget,
+ {?system, 6, 1, _, Vbs_v1}}
+ when is_pid(From_v1) andalso
+ is_list(Vbs_v1) andalso
+ (TDomain =:= transportDomainUdpIpv4) ->
+ ?IPRINT("received expected (v1) handle trap callback message: "
+ "~n ~p", [Vbs_v1]),
+ ok
+
+ after 5000 ->
+ case TDomain of
+ transportDomainUdpIpv4 ->
+ ?IPRINT("TIMEOUT"),
+ ?line exit(timeout);
+ transportDomainUdpIpv6 ->
+ ?IPRINT("expected timeout - "
+ "v1 trap's can only be sent on IPv4 domains"),
+ ok
+ end
+ end,
+
+
+ ?IPRINT("load TestTrapv2..."),
+ ?line ok = otp16649_agent_load_mib(AgentNode, MibDir, "TestTrapv2"),
+
+ ?IPRINT("(agent) send trap (testTrapv22)"),
+ ?line ok = otp16649_agent_send_trap(AgentNode, testTrapv22),
+
+ receive
+ {handle_trap, From_v2, TrapTarget,
+ {noError, 0, Vbs_v2}} when is_pid(From_v2) andalso
+ is_list(Vbs_v2) ->
+ ?IPRINT("received expected (v2) handle trap callback message: "
+ "~n ~p", [Vbs_v2]),
+ ok
+
+ after 5000 ->
+ ?IPRINT("TIMEOUT"),
+ ?line exit(timeout)
+ end,
+
+ ?IPRINT("done"),
+ ok.
+
+
+otp16649_init(N, AgentPreTransports, Config) ->
+ ?IPRINT("otp16649_init -> entry with"
+ "~n N: ~w"
+ "~n AgentPreTransports: ~w"
+ "~n Config: ~p", [N, AgentPreTransports, Config]),
+
+ %% --
+ %% Start nodes
+ %%
+
+ ?IPRINT("start (agent and mansger) nodes"),
+
+ {ok, AgentNode} = start_node(otp16649_mk_name(N, agent)),
+ {ok, ManagerNode} = start_node(otp16649_mk_name(N, manager)),
+
+ %% --
+ %% Misc
+ %%
+
+ AgentHost = ?HOSTNAME(AgentNode),
+ ManagerHost = ?HOSTNAME(ManagerNode),
+
+ ?IPRINT("otp16649_init -> "
+ "~n AgentHost: ~p"
+ "~n ManagerHost: ~p",
+ [AgentHost, ManagerHost]),
+
+ Host = snmp_test_lib:hostname(),
+ Ip = ?config(ip, Config),
+ %% We should really "extract" the address from the hostnames,
+ %% but because on some OSes (Ubuntu) adds 12.7.0.1.1 to its hosts file,
+ %% this does not work. We want a "proper" address.
+ %% Also, since both nodes (agent and manager) are both started locally,
+ %% we can use 'Ip' for both!
+ %% {ok, AgentIP} = snmp_misc:ip(AgentHost),
+ %% {ok, ManagerIP0} = snmp_misc:ip(ManagerHost),
+ AgentIP = Ip,
+ ManagerIP0 = Ip,
+ ManagerIP = tuple_to_list(ManagerIP0),
+ ?IPRINT("otp16649_init -> "
+ "~n Host: ~p"
+ "~n Ip: ~p"
+ "~n AgentIP: ~p"
+ "~n ManagerIP0: ~p"
+ "~n ManagerIP: ~p",
+ [Host, Ip, AgentIP, ManagerIP0, ManagerIP]),
+
+
+ %% --
+ %% Write agent config
+ %%
+
+ AgentConfDir = ?config(agent_conf_dir, Config),
+ Vsns = [v1,v2],
+ TransportDomain = ?config(tdomain, Config),
+ F = fun({PortInfo, Kind}) ->
+ #{addr => {AgentIP, PortInfo}, kind => Kind};
+ ({PortInfo, Kind, Opts}) ->
+ #{addr => {AgentIP, PortInfo}, kind => Kind, opts => Opts}
+ end,
+ AgentPreTransports2 = [F(T) || T <- AgentPreTransports],
+
+ ?IPRINT("write agent config files"),
+ ?line ok = snmp_config:write_agent_snmp_files(
+ AgentConfDir, Vsns,
+ TransportDomain, {ManagerIP, ?MGR_PORT}, AgentPreTransports2,
+ "test"),
+
+ ?IPRINT("start agent"),
+ Config2 = start_agent([{host, Host},
+ {agent_node, AgentNode},
+ {agent_host, AgentHost},
+ {agent_ip, AgentIP},
+ {manager_node, ManagerNode},
+ {manager_host, ManagerHost},
+ {manager_ip, ManagerIP}|Config]),
+
+
+
+ %% --
+ %% Write manager config
+ %%
+
+ ?IPRINT("create manager dirs"),
+ MgrTopDir = ?config(manager_top_dir, Config),
+ MgrDbDir = filename:join(MgrTopDir, "db/"),
+ MgrConfDir = filename:join(MgrTopDir, "conf/"),
+ ?line ok = file:make_dir(MgrConfDir),
+ MgrDbDir = filename:join(MgrTopDir, "db/"),
+ ?line ok = file:make_dir(MgrDbDir),
+ MgrLogDir = filename:join(MgrTopDir, "log/"),
+ ?line ok = file:make_dir(MgrLogDir),
+
+ ?IPRINT("write manager config files"),
+ MgrTransports = [{TransportDomain, {ManagerIP0, ?MGR_PORT}}],
+ ?line ok = snmp_config:write_manager_snmp_files(
+ MgrConfDir,
+ MgrTransports,
+ ?MGR_MMS, ?MGR_ENGINE_ID),
+
+ Config3 = [{manager_db_dir, MgrDbDir},
+ {manager_conf_dir, MgrConfDir},
+ {manager_log_dir, MgrLogDir} | Config2],
+
+ ?IPRINT("start manager"),
+ ?line ok = start_manager(Config3),
+
+ ?DBG("otp16649_init -> done when"
+ "~n Config2: ~p", [Config3]),
+ [{agent_raw_transports, AgentPreTransports} | Config3].
+
+otp16649_mk_name(N, Post) when is_integer(N) andalso is_atom(Post) ->
+ list_to_atom(?F("otp16649_~w_~w", [N, Post])).
+
+
+otp16649_fin(N, Config) when is_integer(N) ->
+ ?IPRINT("otp16649_fin -> entry with"
+ "~n N: ~p"
+ "~n Config: ~p", [N, Config]),
+
+ ManagerNode = ?config(manager_node, Config),
+ AgentNode = ?config(agent_node, Config),
+
+ %% -
+ %% Stop agent (this is the nice way to do it,
+ %% so logs and files can be closed in the proper way).
+ %%
+
+ ?line AgentTopSup = ?config(agent_sup, Config),
+ stop_standalone_agent(AgentTopSup),
+
+
+ %% -
+ %% Stop manager
+ %%
+
+ stop_standalone_manager(ManagerNode),
+
+
+ %%
+ %% Stop the manager node
+ %%
+
+ ?DBG("otp16649_fin -> stop manager node", []),
+ stop_node(ManagerNode),
+
+
+ %%
+ %% Stop the agent node
+ %%
+
+ ?DBG("otp16649_fin -> stop agent node", []),
+ stop_node(AgentNode),
+
+ ?DBG("otp16649_fin -> done", []),
+ Config1 = lists:keydelete(manager_node, 1, Config),
+ lists:keydelete(agent_node, 1, Config1).
+
+
+otp16649_validate_transports([], []) ->
+ ok;
+otp16649_validate_transports([AgentRawTransport|AgentRawTransports],
+ [TI|TIs]) ->
+ otp16649_validate_transport(AgentRawTransport, TI),
+ otp16649_validate_transports(AgentRawTransports, TIs).
+
+otp16649_validate_transport({PortInfo, Kind}, {PortNo, Kind, _}) ->
+ ?IPRINT("validate ~w transport:"
+ "~n PortNo: ~w"
+ "~n PortInfo: ~p", [Kind, PortNo, PortInfo]),
+ otp16649_validate_port(PortInfo, PortNo);
+otp16649_validate_transport({_, ConfKind}, {PortNo, ActualKind, _}) ->
+ exit({invalid_transport_kind, {PortNo, ConfKind, ActualKind}});
+otp16649_validate_transport({PortInfo, Kind, _}, {PortNo, Kind, _}) ->
+ ?IPRINT("validate ~w transport:"
+ "~n PortNo: ~w"
+ "~n PortInfo: ~p", [Kind, PortNo, PortInfo]),
+ otp16649_validate_port(PortInfo, PortNo);
+otp16649_validate_transport({_, ConfKind, _}, {PortNo, ActualKind, _}) ->
+ exit({invalid_transport_kind, {PortNo, ConfKind, ActualKind}}).
+
+otp16649_validate_port(PortNo, PortNo) when is_integer(PortNo) ->
+ ok;
+otp16649_validate_port(0, PortNo) when is_integer(PortNo) ->
+ ok;
+otp16649_validate_port(system, PortNo) when is_integer(PortNo) ->
+ ok;
+otp16649_validate_port(Range, PortNo) when is_tuple(Range) ->
+ case otp16649_validate_port_range(Range, PortNo) of
+ ok ->
+ ok;
+ error ->
+ exit({invalid_transport_port_no, {Range, PortNo}})
+ end;
+otp16649_validate_port(Ranges, PortNo) when is_list(Ranges) ->
+ case otp16649_validate_port_ranges(Ranges, PortNo) of
+ ok ->
+ ok;
+ error ->
+ exit({invalid_transport_port_no, {Ranges, PortNo}})
+ end.
+
+
+otp16649_validate_port_range({Min, Max}, PortNo)
+ when is_integer(Min) andalso
+ is_integer(Max) andalso
+ is_integer(PortNo) andalso
+ (Min =< PortNo) andalso
+ (PortNo =< Max) ->
+ ok;
+otp16649_validate_port_range(_Range, _PortNo) ->
+ error.
+
+otp16649_validate_port_ranges([], _PortNo) ->
+ error;
+otp16649_validate_port_ranges([PortNo|_], PortNo) when is_integer(PortNo) ->
+ ok;
+otp16649_validate_port_ranges([Range|Ranges], PortNo) when is_tuple(Range) ->
+ case otp16649_validate_port_range(Range, PortNo) of
+ ok ->
+ ok;
+ error ->
+ otp16649_validate_port_ranges(Ranges, PortNo)
+ end;
+otp16649_validate_port_ranges([_|Ranges], PortNo) ->
+ otp16649_validate_port_ranges(Ranges, PortNo).
+
+
+otp16649_which_req_port_no(TIs) ->
+ ?IPRINT("otp16649_which_req_port_no -> entry with"
+ "~n TIs: ~p", [TIs]),
+ otp16649_which_port_no(TIs, req_responder).
+
+otp16649_which_trap_port_no(TIs) ->
+ ?IPRINT("otp16649_which_trap_port_no -> entry with"
+ "~n TIs: ~p", [TIs]),
+ otp16649_which_port_no(TIs, trap_sender).
+
+otp16649_which_port_no([], Kind) ->
+ exit({no_transport_port_no, Kind});
+otp16649_which_port_no([{PortNo, Kind, _}|_], Kind) ->
+ PortNo;
+otp16649_which_port_no([_|TIs], Kind) ->
+ otp16649_which_port_no(TIs, Kind).
+
+
+otp16649_mgr_reg_user(Node) ->
+ rpc:call(Node, snmpm, register_user,
+ [otp16649, snmp_otp16649_user, self()]).
+
+otp16649_mgr_reg_agent(Node, IPFam, TDomain, Target, PortNo) ->
+ ?IPRINT("otp16649_mgr_reg_agent -> entry with"
+ "~n Node: ~p"
+ "~n IPFam: ~p"
+ "~n TDomain: ~p"
+ "~n Target: ~p"
+ "~n PortNo: ~p",
+ [Node, IPFam, TDomain, Target, PortNo]),
+ Localhost = ?LOCALHOST(IPFam),
+ Config = [{address, Localhost},
+ {port, PortNo},
+ {version, v1},
+ {tdomain, TDomain},
+ {engine_id, "agentEngine"}],
+ rpc:call(Node, snmpm, register_agent,
+ [otp16649, Target, Config]).
+
+otp16649_mgr_get_req(Node, Oids) ->
+ TargetName = "otp16649-agent-req",
+ rpc:call(Node, snmpm, sync_get2, [otp16649, TargetName, Oids]).
+
+otp16649_agent_load_mib(Node, MibDir, Mib) ->
+ rpc:call(Node, snmpa, unload_mib, [snmp_master_agent, Mib]), % For safety
+ MibPath = join(MibDir, Mib),
+ rpc:call(Node, snmpa, load_mib, [snmp_master_agent, MibPath]).
+
+otp16649_agent_send_trap(Node, Trap) ->
+ rpc:call(Node, snmpa, send_trap,
+ [snmp_master_agent, Trap, "standard trap"]).
+
+
+%%-----------------------------------------------------------------
+
agent_log_validation(Node) ->
rpc:call(Node, ?MODULE, agent_log_validation, []).
@@ -7421,7 +8100,7 @@ start_agent(Config, Opts) ->
process_flag(trap_exit, true),
- AgentTopSup = start_standalone_agent(AgentNode, AgentConfig),
+ ?line {ok, AgentTopSup} = start_standalone_agent(AgentNode, AgentConfig),
[{agent_sup, AgentTopSup} | Config].
@@ -7473,9 +8152,9 @@ start_standalone_agent(Config) ->
case snmpa_supervisor:start_link(normal, Config) of
{ok, AgentTopSup} ->
unlink(AgentTopSup),
- AgentTopSup;
+ {ok, AgentTopSup};
{error, {already_started, AgentTopSup}} ->
- AgentTopSup;
+ {ok, AgentTopSup};
{error, _} = ERROR ->
ERROR
end.
@@ -7499,6 +8178,31 @@ stop_standalone_agent(Pid) ->
nkill(Pid, kill).
+start_manager(Config) ->
+ Node = ?config(manager_node, Config),
+ ConfDir = ?config(manager_conf_dir, Config),
+ DbDir = ?config(manager_db_dir, Config),
+
+ Opts = [{server, [{verbosity, trace}]},
+ {net_if, [{verbosity, trace}]},
+ {note_store, [{verbosity, trace}]},
+ {config, [{verbosity, trace}, {dir, ConfDir}, {db_dir, DbDir}]}],
+ ?line ok = start_standalone_manager(Node, Opts).
+
+
+start_standalone_manager(Node, Config) ->
+ rpc:call(Node, ?MODULE, start_standalone_manager, [Config]).
+
+start_standalone_manager(Config) ->
+ snmpm:start(Config).
+
+
+stop_standalone_manager(Node) when (Node =/= node()) ->
+ rpc:call(Node, snmpm, stop, []);
+stop_standalone_manager(_) ->
+ snmpm:stop().
+
+
nkill(Pid, Reason) ->
nkill(Pid, Reason, 10).
@@ -7536,7 +8240,7 @@ do_info(MaNode) ->
Keys = [vsns,
stats_counters,
{agent, [process_memory, db_memory]},
- {net_if, [process_memory, port_info, reqs]},
+ {net_if, [process_memory, transport_info, reqs]},
{note_store, [process_memory, db_memory]},
{symbolic_store, [process_memory, db_memory]},
{local_db, [process_memory, db_memory]},
diff --git a/lib/snmp/test/snmp_agent_mibs_SUITE.erl b/lib/snmp/test/snmp_agent_mibs_SUITE.erl
index 39946ba7d1..ce6ec80322 100644
--- a/lib/snmp/test/snmp_agent_mibs_SUITE.erl
+++ b/lib/snmp/test/snmp_agent_mibs_SUITE.erl
@@ -189,7 +189,7 @@ init_per_testcase2(size_check_mnesia, Config) when is_list(Config) ->
Config;
init_per_testcase2(cache_test, Config) when is_list(Config) ->
Factor = ?config(snmp_factor, Config),
- ct:timetrap(?MINS(5 + (Factor div 2))),
+ ct:timetrap(?MINS(10 + (Factor div 2))),
Config;
init_per_testcase2(_Case, Config) when is_list(Config) ->
Factor = ?config(snmp_factor, Config),
@@ -206,8 +206,6 @@ end_per_testcase1(size_check_mnesia, Config) when is_list(Config) ->
mnesia_stop(),
Config;
end_per_testcase1(cache_test, Config) when is_list(Config) ->
- Dog = ?config(watchdog, Config),
- test_server:timetrap_cancel(Dog),
Config;
end_per_testcase1(_Case, Config) when is_list(Config) ->
Config.
@@ -552,9 +550,10 @@ cache_test(Config) when is_list(Config) ->
fun() -> do_cache_test(Config) end).
do_cache_test(Config) ->
- ?DBG("do_cache_test -> start", []),
+ ?IPRINT("cache_test -> start"),
Prio = normal,
- Verbosity = trace,
+ %% Verbosity = trace,
+ Verbosity = info,
MibStorage = [{module, snmpa_mib_storage_ets}],
MibDir = ?config(data_dir, Config),
StdMibDir = filename:join(code:priv_dir(snmp), "mibs") ++ "/",
@@ -565,77 +564,213 @@ do_cache_test(Config) ->
"SNMP-MPD-MIB",
"SNMP-NOTIFICATION-MIB",
"SNMP-TARGET-MIB",
- %% "SNMP-USER-BASED-SM-MIB",
+ "SNMP-USER-BASED-SM-MIB",
"SNMP-VIEW-BASED-ACM-MIB",
"SNMPv2-MIB",
"SNMPv2-TC",
"SNMPv2-TM"],
- ?DBG("cache_test -> start symbolic store", []),
- ?line sym_start(Prio, MibStorage, Verbosity),
+ ?IPRINT("cache_test -> start symbolic store"),
+ ?line sym_start(Prio, MibStorage, silence), % Verbosity),
- ?DBG("cache_test -> start mib server", []),
- GcLimit = 2,
- Age = timer:seconds(10),
- CacheOpts = [{autogc, false}, {age, Age}, {gclimit, GcLimit}],
+ ?IPRINT("cache_test -> start mib server"),
+ GcLimit = 3,
+ Age = timer:seconds(10),
+ CacheOpts = [{autogc, false},
+ {age, Age},
+ {gclimit, GcLimit},
+ {gcverbose, true}],
?line MibsPid = mibs_start(Prio, MibStorage, [], Verbosity, CacheOpts),
- ?DBG("cache_test -> load mibs", []),
+ ?NPRINT("Info before load mibs: "
+ "~n ~p", [snmpa_mib:info(MibsPid)]),
+
+ ?IPRINT("cache_test -> load mibs"),
?line load_mibs(MibsPid, MibDir, Mibs),
- ?DBG("cache_test -> load std mibs", []),
+
+ ?NPRINT("Info before load std mibs: "
+ "~n ~p", [snmpa_mib:info(MibsPid)]),
+
+ ?IPRINT("cache_test -> load std mibs"),
?line load_mibs(MibsPid, StdMibDir, StdMibs),
- ?DBG("cache_test -> do a simple walk to populate the cache", []),
- ?line ok = walk(MibsPid),
-
- {ok, Sz1} = snmpa_mib:which_cache_size(MibsPid),
- ?DBG("cache_test -> Size1: ~p", [Sz1]),
+ ?NPRINT("Info (after mibs load but) before populate: "
+ "~n ~p", [snmpa_mib:info(MibsPid)]),
+
+ ?IPRINT("cache_test -> populate the cache"),
+ ?line ok = populate(MibsPid),
- ?DBG("cache_test -> sleep 5 secs", []),
+ ?NPRINT("Info after populate: "
+ "~n ~p", [snmpa_mib:info(MibsPid)]),
+
+ Sz1 = cache_sz_verify(1, MibsPid, any),
+
+ ?IPRINT("cache_test -> sleep 5 secs"),
?SLEEP(timer:seconds(5)),
- ?DBG("cache_test -> perform gc, expect nothing", []),
- {ok, 0} = snmpa_mib:gc_cache(MibsPid),
+ _ = cache_gc_verify(1, MibsPid),
+
+ ?NPRINT("Info after 5 sec sleep: "
+ "~n ~p", [snmpa_mib:info(MibsPid)]),
- ?DBG("cache_test -> sleep 10 secs", []),
+ ?IPRINT("cache_test -> sleep 10 secs"),
?SLEEP(timer:seconds(10)),
- ?DBG("cache_test -> perform gc, expect GcLimit", []),
- GcLimit1 = GcLimit + 1,
- {ok, GcLimit1} = snmpa_mib:gc_cache(MibsPid, Age, GcLimit1),
+ ?NPRINT("Info after 10 sec sleep: "
+ "~n ~p", [snmpa_mib:info(MibsPid)]),
+
+ GcLimit1 = cache_gc_verify(2, MibsPid, Age, GcLimit + 1),
+
+ Sz2 = cache_sz_verify(2, MibsPid, Sz1 - GcLimit1),
+
- Sz2 = Sz1 - GcLimit1,
- {ok, Sz2} = snmpa_mib:which_cache_size(MibsPid),
- ?DBG("cache_test -> Size2: ~p", [Sz2]),
+ ?IPRINT("cache_test -> subscribe to GC events"),
+ ?line ok = snmpa_mib:subscribe_gc_events(MibsPid),
- ?DBG("cache_test -> enable cache autogc", []),
+ ?IPRINT("cache_test -> enable cache autogc"),
?line ok = snmpa_mib:enable_cache_autogc(MibsPid),
- ?DBG("cache_test -> wait 65 seconds to allow gc to happen", []),
+ ?IPRINT("cache_test -> wait 65 seconds to allow gc to happen"),
?SLEEP(timer:seconds(65)),
- Sz3 = Sz2 - GcLimit,
- {ok, Sz3} = snmpa_mib:which_cache_size(MibsPid),
- ?DBG("cache_test -> Size3: ~p", [Sz3]),
- ?DBG("cache_test -> "
- "wait 2 minutes to allow gc to happen, expect empty cache", []),
+ ?NPRINT("Info after 65 sec sleep: "
+ "~n ~p", [snmpa_mib:info(MibsPid)]),
+
+ ?IPRINT("cache_test -> [1] flush expected GC events"),
+ {NumEvents1, TotGC1} = cache_flush_gc_events(MibsPid),
+ ?IPRINT("cache_test -> GC events: "
+ "~n Number of Events: ~p"
+ "~n Total elements GCed: ~p", [NumEvents1, TotGC1]),
+
+ _ = cache_sz_verify(3, MibsPid, Sz2 - GcLimit),
+
+ ?IPRINT("cache_test -> "
+ "wait 2 minutes to allow gc to happen, expect empty cache"),
?SLEEP(timer:minutes(2)),
- {ok, 0} = snmpa_mib:which_cache_size(MibsPid),
- ?DBG("cache_test -> stop mib server", []),
+ ?NPRINT("Info after 2 min sleep: "
+ "~n ~p", [snmpa_mib:info(MibsPid)]),
+
+ _ = cache_sz_verify(4, MibsPid, 0),
+
+
+ ?IPRINT("cache_test -> change gclimit to infinity"),
+ snmpa_mib:update_cache_gclimit(MibsPid, infinity),
+
+ ?IPRINT("cache_test -> change age to ~w mins", [3]),
+ snmpa_mib:update_cache_age(MibsPid, ?MINS(3)),
+
+ ?IPRINT("cache_test -> [2] flush expected GC events"),
+ {NumEvents2, TotGC2} = cache_flush_gc_events(MibsPid),
+ ?IPRINT("cache_test -> GC events: "
+ "~n Number of Events: ~p"
+ "~n Total elements GCed: ~p", [NumEvents2, TotGC2]),
+
+ ?IPRINT("cache_test -> populate the cache again"),
+ populate(MibsPid),
+
+ ?IPRINT("cache_test -> validate cache size"),
+ {ok, Sz4} = snmpa_mib:which_cache_size(MibsPid),
+ if (Sz4 > 0) ->
+ ?IPRINT("cache_test -> expected cache size: ~w > 0", [Sz4]);
+ true ->
+ ?EPRINT("cache_test -> cache *not* populated"),
+ ?FAIL(cache_not_populated)
+ end,
+
+ ?NPRINT("Info after poulated: "
+ "~n ~p", [snmpa_mib:info(MibsPid)]),
+
+ ?IPRINT("cache_test -> wait 2 mins - before tuching some entries"),
+ ?SLEEP(?MINS(2)),
+
+ %% There should not be anything GC:ed
+
+ receive
+ {MibsPid, gc_result, {ok, NGC1}} ->
+ ?EPRINT("cache_test -> unexpected GC of ~w elements", [NGC1]),
+ exit({unexpected_gc_result, NGC1})
+ after 0 ->
+ ok
+ end,
+
+ ?IPRINT("cache_test -> touch some elements again (update the cache)"),
+ populate_lookup(MibsPid),
+
+ ?IPRINT("cache_test -> await partial GC"),
+ NumGC2 =
+ receive
+ {MibsPid, gc_result, {ok, NGC2}}
+ when (NGC2 > 0) andalso (Sz4 > NGC2) ->
+ ?NPRINT("cache_test -> "
+ "received partial GC result of ~w elements", [NGC2]),
+ NGC2
+ end,
+
+ ?NPRINT("Info after partial GC: "
+ "~n ~p", [snmpa_mib:info(MibsPid)]),
+
+
+ ?IPRINT("cache_test -> await final GC"),
+ receive
+ {MibsPid, gc_result, {ok, NGC3}}
+ when (NGC3 > 0) andalso ((Sz4 - NumGC2) =:= NGC3) ->
+ ?NPRINT("cache_test -> "
+ "received final GC result of ~w elements", [NGC3]),
+ NGC3;
+ Any ->
+ ?EPRINT("cache_test -> unexpected message: "
+ "~n ~p", [Any]),
+ ?FAIL({unexpected, Any})
+ end,
+
+ ?NPRINT("Info after final GC: "
+ "~n ~p", [snmpa_mib:info(MibsPid)]),
+
+ ?IPRINT("cache_test -> validate cache size (expect empty)"),
+ {ok, Sz5} = snmpa_mib:which_cache_size(MibsPid),
+ if (Sz5 =:= 0) ->
+ ?IPRINT("cache_test -> expected cache size: 0");
+ true ->
+ ?EPRINT("cache_test -> cache *not* empty (~w)", [Sz5]),
+ ?FAIL({cache_populated, Sz5})
+ end,
+
+
+ ?IPRINT("cache_test -> stop mib server"),
?line mibs_stop(MibsPid),
- ?DBG("cache_test -> stop symbolic store", []),
+ ?IPRINT("cache_test -> stop symbolic store"),
?line sym_stop(),
+
+ ?IPRINT("cache_test -> end"),
ok.
-walk(MibsPid) ->
+populate(MibsPid) ->
+ %% Make some lookups
+ populate_lookup(MibsPid),
+ %% Make some walk's
+ populate_walk(MibsPid).
+
+populate_lookup(MibsPid) ->
+ {variable, _} = snmpa_mib:lookup(MibsPid, ?snmpTrapCommunity_instance),
+ {variable, _} = snmpa_mib:lookup(MibsPid, ?vacmViewSpinLock_instance),
+ {variable, _} = snmpa_mib:lookup(MibsPid, ?usmStatsNotInTimeWindows_instance),
+ {variable, _} = snmpa_mib:lookup(MibsPid, ?tDescr_instance),
+ ok.
+
+populate_walk(MibsPid) ->
MibView = snmpa_acm:get_root_mib_view(),
- do_walk(MibsPid, ?snmpTrapCommunity_instance, MibView),
- do_walk(MibsPid, ?vacmViewSpinLock_instance, MibView),
- do_walk(MibsPid, ?usmStatsNotInTimeWindows_instance, MibView),
- do_walk(MibsPid, ?tDescr_instance, MibView).
-
+ walk(MibsPid, ?snmpTrapCommunity_instance, MibView),
+ walk(MibsPid, ?vacmViewSpinLock_instance, MibView),
+ walk(MibsPid, ?usmStatsNotInTimeWindows_instance, MibView),
+ walk(MibsPid, ?tDescr_instance, MibView),
+ ok.
+
+walk(MibsPid, Oid, MibView) ->
+ ?IPRINT("walk -> entry with"
+ "~n Oid: ~p", [Oid]),
+ do_walk(MibsPid, Oid, MibView).
do_walk(MibsPid, Oid, MibView) ->
?IPRINT("do_walk -> entry with"
@@ -646,15 +781,89 @@ do_walk(MibsPid, Oid, MibView) ->
?IPRINT("do_walk -> done for table (~p)", [Oid]),
ok;
{table, _, _, #me{oid = Next}} ->
+ ?IPRINT("do_walk -> table next ~p", [Next]),
do_walk(MibsPid, Next, MibView);
{variable, #me{oid = Oid}, _} ->
?IPRINT("do_walk -> done for variable (~p)", [Oid]),
ok;
{variable, #me{oid = Next}, _} ->
+ ?IPRINT("do_walk -> variable next ~p", [Next]),
do_walk(MibsPid, Next, MibView)
end.
+cache_gc_verify(ID, MibsPid) ->
+ GC = fun() -> snmpa_mib:gc_cache(MibsPid) end,
+ cache_gc_verify(ID, GC, 0).
+
+cache_gc_verify(ID, MibsPid, Age, ExpectedGcLimit) ->
+ GC = fun() -> snmpa_mib:gc_cache(MibsPid, Age, ExpectedGcLimit) end,
+ cache_gc_verify(ID, GC, ExpectedGcLimit).
+
+cache_gc_verify(ID, GC, ExpectedGc) ->
+ ?IPRINT("cache_gc_verify -> [~w] perform gc, expect ~w", [ID, ExpectedGc]),
+ case GC() of
+ {ok, ExpectedGc} ->
+ ?IPRINT("cache_gc_verify -> [~w] gc => ok", [ID]),
+ ExpectedGc;
+ {ok, OtherGc} ->
+ ?IPRINT("cache_gc_verify -> [~w] invalid GC limit: "
+ "~n Expected: ~p"
+ "~n Got: ~p"
+ "~n ~p",
+ [ID, 0, OtherGc]),
+ exit({ID, invalid_gc_limit, {ExpectedGc, OtherGc}});
+ Unexpected ->
+ ?IPRINT("cache_gc_verify -> [~w] unexpected: "
+ "~n ~p",
+ [ID, Unexpected]),
+ exit({ID, unexpected, Unexpected})
+ end.
+
+
+cache_sz_verify(ID, MibsPid, ExpectedSz) ->
+ ?IPRINT("cache_sz_verify -> [~w] expect size ~w", [ID, ExpectedSz]),
+ case snmpa_mib:which_cache_size(MibsPid) of
+ {ok, ExpectedSz} ->
+ ?IPRINT("cache_sz_verify -> [~w] sz => ok", [ID]),
+ ExpectedSz;
+ {ok, UnexpectedSz} when (ExpectedSz =:= any) ->
+ ?IPRINT("cache_sz_verify -> [~w] sz => ok (~w)", [ID, UnexpectedSz]),
+ UnexpectedSz;
+ {ok, UnexpectedSz} ->
+ ?IPRINT("cache_sz_verify -> [~w] invalid size: "
+ "~n Expected: ~p"
+ "~n Got: ~p",
+ [ID, ExpectedSz, UnexpectedSz]),
+ exit({ID, invalid_size, {ExpectedSz, UnexpectedSz}});
+ Unexpected ->
+ ?IPRINT("cache_sz_verify -> [~w] unexpected: "
+ "~n ~p",
+ [ID, Unexpected]),
+ exit({ID, unexpected, Unexpected})
+ end.
+
+
+cache_flush_gc_events(MibServer) ->
+ cache_flush_gc_events(MibServer, 0, 0).
+
+cache_flush_gc_events(MibServer, NumEvents, TotGC) ->
+ receive
+ {MibServer, gc_result, {ok, NumGC}} ->
+ ?IPRINT("cache_flush_gc_events -> GC event ~w (~w)",
+ [NumGC, NumEvents]),
+ cache_flush_gc_events(MibServer, NumEvents+1, TotGC+NumGC)
+ after 0 ->
+ if
+ (NumEvents =:= 0) andalso (TotGC =:= 0) ->
+ ?IPRINT("cache_flush_gc_events -> no GC events"),
+ exit(no_gc_events);
+ true ->
+ {NumEvents, TotGC}
+ end
+ end.
+
+
%%======================================================================
%% Internal functions
%%======================================================================
diff --git a/lib/snmp/test/snmp_agent_test_lib.erl b/lib/snmp/test/snmp_agent_test_lib.erl
index 629ec49db4..da2762c3fb 100644
--- a/lib/snmp/test/snmp_agent_test_lib.erl
+++ b/lib/snmp/test/snmp_agent_test_lib.erl
@@ -26,7 +26,7 @@
start_v2_agent/1, start_v2_agent/2,
start_v3_agent/1, start_v3_agent/2,
start_bilingual_agent/1, start_bilingual_agent/2,
- start_mt_agent/1, start_mt_agent/2,
+ start_mt_agent/1, start_mt_agent/2, start_mt_agent/3,
stop_agent/1,
%% start_sup/0, stop_sup/2,
@@ -475,22 +475,28 @@ tc_run(Mod, Func, Args, Opts) ->
"~n StdM: ~p"
"~n", [M,Vsn,Dir,User,SecLevel,EngineID,CtxEngineID,Community,StdM]),
case snmp_test_mgr:start_link([%% {agent, snmp_test_lib:hostname()},
- {packet_server_debug, true},
- {debug, false},
- {agent, get(master_host)},
- {ipfamily, get(ipfamily)},
- {agent_udp, 4000},
- {trap_udp, 5000},
- {recbuf, 65535},
- quiet,
- Vsn,
- {community, Community},
- {user, User},
- {sec_level, SecLevel},
- {engine_id, EngineID},
- {context_engine_id, CtxEngineID},
- {dir, Dir},
- {mibs, mibs(StdM, M)}]) of
+ {packet_server_debug, true},
+ {debug, false},
+ {agent, get(master_host)},
+ {ipfamily, get(ipfamily)},
+ {agent_udp, 4000},
+ %% <SEP-TRANSPORTS>
+ %% First port is used to request replies
+ %% Second port is used for traps sent
+ %% by the agent.
+ %% {agent_udp, {4000, 4001}},
+ %% </SEP-TRANSPORTS>
+ {trap_udp, 5000},
+ {recbuf, 65535},
+ quiet,
+ Vsn,
+ {community, Community},
+ {user, User},
+ {sec_level, SecLevel},
+ {engine_id, EngineID},
+ {context_engine_id, CtxEngineID},
+ {dir, Dir},
+ {mibs, mibs(StdM, M)}]) of
{ok, _Pid} ->
case (catch apply(Mod, Func, Args)) of
{'EXIT', {skip, Reason}} ->
@@ -567,12 +573,18 @@ start_bilingual_agent(Config, Opts)
when is_list(Config) andalso is_list(Opts) ->
start_agent(Config, [v1,v2], Opts).
-start_mt_agent(Config) when is_list(Config) ->
- start_agent(Config, [v2], [{multi_threaded, true}]).
-
-start_mt_agent(Config, Opts) when is_list(Config) andalso is_list(Opts) ->
- start_agent(Config, [v2], [{multi_threaded, true}|Opts]).
+start_mt_agent(Config) ->
+ start_mt_agent(Config, true, []).
+start_mt_agent(Config, MT) ->
+ start_mt_agent(Config, MT, []).
+
+start_mt_agent(Config, MT, Opts)
+ when is_list(Config) andalso
+ ((MT =:= true) orelse (MT =:= extended)) andalso
+ is_list(Opts) ->
+ start_agent(Config, [v2], [{multi_threaded, MT} | Opts]).
+
start_agent(Config, Vsns) ->
start_agent(Config, Vsns, []).
start_agent(Config, Vsns, Opts) ->
@@ -1584,14 +1596,28 @@ config(Vsns, MgrDir, AgentConfDir, MIp, AIp, IpFamily) ->
?line {Domain, ManagerAddr} =
case IpFamily of
inet6 ->
- Ipv6Domain = transportDomainUdpIpv6,
- AgentIpv6Addr = {AIp, 4000},
- ManagerIpv6Addr = {MIp, ?TRAP_UDP},
+ TransportDomain6 = transportDomainUdpIpv6,
+ AgentAddr6 = {AIp, 4000},
+ ManagerAddr6 = {MIp, ?TRAP_UDP},
+ ?line ok =
+ snmp_config:write_agent_snmp_files(
+ AgentConfDir, Vsns,
+ TransportDomain6, ManagerAddr6, AgentAddr6, "test"),
+ {TransportDomain6, ManagerAddr6};
+ inet ->
+ TransportDomain4 = transportDomainUdpIpv4,
+ AIp2 = maybe_fix_addr(AIp),
+ ManagerAddr4 = {MIp, ?TRAP_UDP},
+ %% AgentPreTransport =
+ %% [#{addr => {AIp2, 4000}, kind => req_responder},
+ %% #{addr => {AIp2, 4001}, kind => trap_sender}],
+ AgentPreTransport = [#{addr => {AIp2, 4000}}],
?line ok =
snmp_config:write_agent_snmp_files(
AgentConfDir, Vsns,
- Ipv6Domain, ManagerIpv6Addr, AgentIpv6Addr, "test"),
- {Ipv6Domain, ManagerIpv6Addr};
+ TransportDomain4, ManagerAddr4, AgentPreTransport,
+ "test"),
+ {TransportDomain4, ManagerAddr4};
_ ->
?line ok =
snmp_config:write_agent_snmp_files(
@@ -1614,6 +1640,12 @@ config(Vsns, MgrDir, AgentConfDir, MIp, AIp, IpFamily) ->
?line write_notify_conf(AgentConfDir),
ok.
+maybe_fix_addr(Addr) when is_list(Addr) ->
+ list_to_tuple(Addr);
+maybe_fix_addr(Addr) when is_tuple(Addr) ->
+ Addr.
+
+
delete_files(Config) ->
AgentDir = ?config(agent_dir, Config),
delete_files(AgentDir, [db, conf]).
diff --git a/lib/snmp/test/snmp_conf_SUITE.erl b/lib/snmp/test/snmp_conf_SUITE.erl
index 7d60485060..698cf2d6f3 100644
--- a/lib/snmp/test/snmp_conf_SUITE.erl
+++ b/lib/snmp/test/snmp_conf_SUITE.erl
@@ -28,6 +28,7 @@
%% Include files
%%----------------------------------------------------------------------
+-include_lib("kernel/include/file.hrl").
-include_lib("common_test/include/ct.hrl").
-include("snmp_test_lib.hrl").
@@ -60,7 +61,9 @@
check_timer/1,
read/1,
- read_files/1
+ read_files/1,
+
+ fd_leak_check/1
]).
@@ -85,7 +88,8 @@ all() ->
check_sec_model2,
check_sec_level,
check_timer,
- read, read_files
+ read, read_files,
+ fd_leak_check
].
groups() ->
@@ -109,10 +113,15 @@ init_per_suite(Config0) when is_list(Config0) ->
%% We need a monitor on this node also
snmp_test_sys_monitor:start(),
+ PrivDir = ?config(priv_dir, Config1),
+ PrivSubdir = filename:join(PrivDir, "snmp_conf_test"),
+ ok = filelib:ensure_dir(filename:join(PrivSubdir, "dummy")),
+ Config2 = [{priv_subdir, PrivSubdir} | Config1],
+
?IPRINT("init_per_suite -> end when"
- "~n Config: ~p", [Config1]),
+ "~n Config: ~p", [Config2]),
- Config1
+ Config2
end.
end_per_suite(Config0) when is_list(Config0) ->
@@ -144,6 +153,71 @@ end_per_group(_GroupName, Config) ->
%% -----
%%
+init_per_testcase(fd_leak_check = _Case, Config) when is_list(Config) ->
+ ?IPRINT("init_per_testcase -> entry with"
+ "~n Config: ~p", [Config]),
+
+ %% There are other ways to test this:
+ %% lsof (linux and maybe FreeBSD): lsof -p <pid>
+ %% os:cmd("lsof -p " ++ os:getpid() ++ " | grep -v COMMAND | wc -l").
+ %% fstat (FreeBSD, OpenBSD and maybe NetBSD): fstat -p <pid>
+ %% os:cmd("fstat -p " ++ os:getpid() ++ " | grep -v USER | wc -l").
+ %% But this (list:dir) is good enough...
+ case os:type() of
+ {unix, linux} ->
+ ?IPRINT("init_per_testcase -> linux: check proc fs"),
+ case file:read_file_info("/proc/" ++ os:getpid() ++ "/fd") of
+ {ok, #file_info{type = directory,
+ access = Access}}
+ when (Access =:= read) orelse
+ (Access =:= read_write) ->
+ ?IPRINT("init_per_testcase -> linux: usingh proc fs"),
+ [{num_open_fd, fun num_open_fd_using_list_dir/0} |
+ Config];
+ _ ->
+ {skip, "Not proc fd"}
+ end;
+ {unix, solaris} ->
+ %% Something strange happens when we use pfiles from within erlang,
+ %% so skip the test for now
+
+ %% For some reason even though 'which' exists (atleast in
+ %% a tcsh shell), it hangs when called via os:cmd/1.
+ %% And type produces results that is not so easy to
+ %% "analyze". So, we 'try it' and know that it starts
+ %% by writing the pid and program on the first line...
+
+ %% ?IPRINT("init_per_testcase -> solaris: check pfiles"),
+ %% PID = os:getpid(),
+ %% case string:find(os:cmd("pfiles -n " ++ PID), PID) of
+ %% nomatch ->
+ %% {skip, "pfiles not found"};
+ %% _ ->
+ %% ?IPRINT("init_per_testcase -> solaris: pfiles found"),
+ %% [{num_open_fd, fun num_open_fd_using_pfiles/0} |
+ %% Config]
+ %% end;
+ {skip, "pfiles not found"};
+
+ {unix, BSD} when (BSD =:= freebsd) orelse
+ (BSD =:= openbsd) orelse
+ (BSD =:= netbsd) ->
+ ?IPRINT("init_per_testcase -> ~w: check fstat", [BSD]),
+ case os:cmd("which fstat") of
+ [] ->
+ {skip, "fstat not found"};
+ _ ->
+ ?IPRINT("init_per_testcase -> ~w: fstat found", [BSD]),
+ [{num_open_fd, fun num_open_fd_using_fstat/0} |
+ Config]
+ end;
+ {win32, _} ->
+ ?IPRINT("init_per_testcase -> windows: no check"),
+ {skip, "Not implemented"};
+ _ ->
+ ?IPRINT("init_per_testcase -> no check"),
+ {skip, "Not implemented"}
+ end;
init_per_testcase(_Case, Config) when is_list(Config) ->
Config.
@@ -716,6 +790,103 @@ read_files(Config) when is_list(Config) ->
%%======================================================================
+
+%% Since we need something to read, we also write.
+%% And we can just as well check then (after write) too...
+fd_leak_check(suite) -> [];
+fd_leak_check(Config) when is_list(Config) ->
+ ?TC_TRY(fd_leak_check,
+ fun() -> ok end,
+ fun(_) -> do_fd_leak_check(Config) end,
+ fun(_) -> ok end).
+
+do_fd_leak_check(Config) ->
+ Dir = ?config(priv_subdir, Config),
+ NumOpenFD = ?config(num_open_fd, Config),
+
+ %% Create "some" config
+ ?IPRINT("do_fd_leak_check -> create some config"),
+ Entries = [
+ snmpa_conf:agent_entry(intAgentIpAddress, {0,0,0,0}),
+ snmpa_conf:agent_entry(intAgentUDPPort, 161),
+ snmpa_conf:agent_entry(snmpEngineMaxMessageSize, 484),
+ snmpa_conf:agent_entry(snmpEngineID, "fooBarEI")
+ ],
+
+ ?IPRINT("do_fd_leak_check -> get number of FD (before test)"),
+ NumFD01 = NumOpenFD(),
+ ?IPRINT("do_fd_leak_check -> before config write: ~w", [NumFD01]),
+
+ ?IPRINT("do_fd_leak_check -> write config to file"),
+ ok = snmpa_conf:write_agent_config(Dir, Entries),
+
+ NumFD02 = NumOpenFD(),
+ ?IPRINT("do_fd_leak_check -> (after write) before config read: ~w", [NumFD02]),
+
+ {ok, _} = snmpa_conf:read_agent_config(Dir),
+
+
+ NumFD03 = NumOpenFD(),
+ ?IPRINT("do_fd_leak_check -> after config read: ~w", [NumFD03]),
+ if
+ (NumFD01 =:= NumFD02) andalso (NumFD02 =:= NumFD03) ->
+ ?IPRINT("do_fd_leak_check -> fd leak check ok"),
+ ok;
+ true ->
+ ?EPRINT("do_fd_leak_check -> fd leak check failed: "
+ "~n Before write: ~w"
+ "~n Before read: ~w"
+ "~n After read: ~w", [NumFD01, NumFD02, NumFD03]),
+ ?FAIL({num_open_fd, NumFD01, NumFD02, NumFD03})
+ end.
+
+
+
+%% There are other ways to test this:
+%% lsof (linux and maybe FreeBSD): lsof -p <pid>
+%% os:cmd("lsof -p " ++ os:getpid() ++ " | grep -v COMMAND | wc -l").
+%% fstat (FreeBSD and maybe OpenBSD): fstat -p <pid>
+%% os:cmd("fstat -p " ++ os:getpid() ++ " | grep -v USER | wc -l").
+%% But this is good enough...
+num_open_fd_using_list_dir() ->
+ case file:list_dir("/proc/" ++ os:getpid() ++ "/fd") of
+ {ok, FDs} ->
+ length(FDs);
+ {error, Reason} ->
+ ?EPRINT("Failed listing proc fs (for fd): "
+ "~n Reason: ~p", [Reason]),
+ ?SKIP({failed_listing_fd, Reason})
+ end.
+
+
+%% num_open_fd_using_pfiles() ->
+%% NumString = os:cmd("pfiles -n " ++ os:getpid() ++
+%% " | grep -v " ++ os:getpid() ++
+%% " | grep -v \"Current rlimit\" | wc -l"),
+%% try list_to_integer(string:trim(NumString))
+%% catch
+%% C:E ->
+%% ?EPRINT("Failed pfiles: "
+%% "~n Error Class: ~p"
+%% "~n Error: ~p", [C, E]),
+%% ?SKIP({failed_pfiles, C, E})
+%% end.
+
+
+num_open_fd_using_fstat() ->
+ NumString = os:cmd("fstat -p " ++ os:getpid() ++
+ " | grep -v MOUNT | wc -l"),
+ try list_to_integer(string:trim(NumString))
+ catch
+ C:E ->
+ ?EPRINT("Failed fstat: "
+ "~n Error Class: ~p"
+ "~n Error: ~p", [C, E]),
+ ?SKIP({failed_fstat, C, E})
+ end.
+
+
+%%======================================================================
%% Internal functions
%%======================================================================
diff --git a/lib/snmp/test/snmp_manager_SUITE.erl b/lib/snmp/test/snmp_manager_SUITE.erl
index 6cc0b0ebde..6cc84d1e35 100644
--- a/lib/snmp/test/snmp_manager_SUITE.erl
+++ b/lib/snmp/test/snmp_manager_SUITE.erl
@@ -4266,24 +4266,26 @@ do_inform4(Config) ->
inform_swarm_cbp_def(suite) -> [];
inform_swarm_cbp_def(Config) when is_list(Config) ->
- inform_swarm(is_cbp_def, Config).
+ inform_swarm(is_cbp_def, 1500, Config).
inform_swarm_cbp_temp(suite) -> [];
inform_swarm_cbp_temp(Config) when is_list(Config) ->
- inform_swarm(is_cbp_temp, Config).
+ inform_swarm(is_cbp_temp, 1500, Config).
inform_swarm_cbp_perm(suite) -> [];
inform_swarm_cbp_perm(Config) when is_list(Config) ->
- inform_swarm(is_cbp_perm, Config).
+ inform_swarm(is_cbp_perm, 1800, Config).
-inform_swarm(Case, Config) ->
+inform_swarm(Case, NumI, Config) ->
?TC_TRY(Case,
- fun() -> do_inform_swarm(Config) end).
+ fun() -> do_inform_swarm(NumI, Config) end).
-do_inform_swarm(Config) ->
+do_inform_swarm(NumI, Config) ->
%% process_flag(trap_exit, true),
- ?IPRINT("starting with Config: "
- "~p ~n", [Config]),
+ ?IPRINT("starting with"
+ "~n NumI: ~p"
+ "~n Config: ~p"
+ "~n", [NumI, Config]),
MgrNode = ?config(manager_node, Config),
AgentNode = ?config(agent_node, Config),
@@ -4299,7 +4301,7 @@ do_inform_swarm(Config) ->
?line ok = agent_load_mib(AgentNode, Test2Mib),
?line ok = agent_load_mib(AgentNode, TestTrapMib),
?line ok = agent_load_mib(AgentNode, TestTrapv2Mib),
- NumInforms = 2000 div Factor,
+ NumInforms = NumI div Factor,
Collector = self(),
diff --git a/lib/snmp/test/snmp_manager_config_SUITE.erl b/lib/snmp/test/snmp_manager_config_SUITE.erl
index 9a7b485a60..f7f7fd6928 100644
--- a/lib/snmp/test/snmp_manager_config_SUITE.erl
+++ b/lib/snmp/test/snmp_manager_config_SUITE.erl
@@ -2249,6 +2249,7 @@ register_usm_user_using_function(Conf) when is_list(Conf) ->
{auth, usmHMACMD5AuthProtocol},
{auth_key, [1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6]},
{priv, usmNoPrivProtocol}],
+
?line ok = snmpm_config:register_usm_user(EngineID, UserName1, UsmConfig1),
?IPRINT("try register user 1 again (error)"),
?line {error, {already_registered, EngineID, UserName1}} =
@@ -2270,7 +2271,17 @@ register_usm_user_using_function(Conf) when is_list(Conf) ->
{auth_key, [1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6]},
{priv, usmNoPrivProtocol}],
?line ok = snmpm_config:register_usm_user(EngineID, UserName3, UsmConfig3),
-
+
+ ?IPRINT("register user 4 (ok)"),
+ UserName4 = "samu4",
+ SecName4 = "samu_auth4",
+ UsmConfig4 = [{sec_name, SecName4},
+ {auth, usmHMACMD5AuthProtocol},
+ {auth_key, [1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6]},
+ {priv, usmAesCfb128Protocol},
+ {priv_key, [190,54,66,227,33,171,152,0,133,223,204,155,109,111,77,44]}],
+ ?line ok = snmpm_config:register_usm_user(EngineID, UserName4, UsmConfig4),
+
?IPRINT("lookup 1 (ok)"),
?line {ok, #usm_user{name = UserName1} = User1} =
snmpm_config:get_usm_user_from_sec_name(EngineID, SecName1),
@@ -2286,9 +2297,14 @@ register_usm_user_using_function(Conf) when is_list(Conf) ->
snmpm_config:get_usm_user_from_sec_name(EngineID, SecName3),
?IPRINT("User: ~p", [User3]),
- ?IPRINT("lookup 4 (error)"),
+ ?IPRINT("lookup 4 (ok)"),
+ ?line {ok, #usm_user{name = UserName4} = User4} =
+ snmpm_config:get_usm_user_from_sec_name(EngineID, SecName4),
+ ?IPRINT("User: ~p", [User4]),
+
+ ?IPRINT("lookup 5 (error)"),
?line {error, not_found} =
- snmpm_config:get_usm_user_from_sec_name(EngineID, SecName3 ++ "_1"),
+ snmpm_config:get_usm_user_from_sec_name(EngineID, SecName4 ++ "_1"),
%% --
?IPRINT("stop config process"),
@@ -2341,6 +2357,7 @@ update_usm_user_info(Conf) when is_list(Conf) ->
?IPRINT("start"),
process_flag(trap_exit, true),
+ ?IPRINT("Start crypto and ensure support"),
case ?CRYPTO_START() of
ok ->
case ?CRYPTO_SUPPORT() of
@@ -2353,9 +2370,62 @@ update_usm_user_info(Conf) when is_list(Conf) ->
?SKIP({failed_starting_crypto, Reason})
end,
- _ConfDir = ?config(manager_conf_dir, Conf),
- _DbDir = ?config(manager_db_dir, Conf),
- ?SKIP(not_yet_implemented).
+ ConfDir = ?config(manager_conf_dir, Conf),
+ DbDir = ?config(manager_db_dir, Conf),
+
+ ?IPRINT("write manager config"),
+ write_manager_conf(ConfDir),
+
+ Opts = [{versions, [v3]},
+ {config, [{verbosity, trace}, {dir, ConfDir}, {db_dir, DbDir}]}],
+
+ ?IPRINT("Start config server"),
+ ?line {ok, _Pid} = snmpm_config:start_link(Opts),
+
+ ?IPRINT("Register usm user"),
+ EngineID = "engine",
+ UsmUser = "UsmUser",
+ SecName = UsmUser,
+ AuthProto = usmHMACMD5AuthProtocol,
+ AuthKey = [1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6],
+ PrivProto1 = usmNoPrivProtocol,
+ UsmConfig = [{sec_name, SecName},
+ {auth, AuthProto},
+ {auth_key, AuthKey},
+ {priv, PrivProto1}],
+ ok = snmpm_config:register_usm_user(EngineID, UsmUser, UsmConfig),
+
+ ?IPRINT("verify user user config"),
+ ?line {ok, AuthProto} = snmpm_config:usm_user_info(EngineID, UsmUser, auth),
+ ?line {ok, AuthKey} = snmpm_config:usm_user_info(EngineID, UsmUser, auth_key),
+ ?line {ok, PrivProto1} = snmpm_config:usm_user_info(EngineID, UsmUser, priv),
+
+ ?IPRINT("usm user update 1"),
+ PrivProto2 = usmAesCfb128Protocol,
+ PrivKey2 = [190,54,66,227,33,171,152,0,133,223,204,155,109,111,77,44],
+ ok = snmpm_config:update_usm_user_info(EngineID, UsmUser, priv, PrivProto2),
+ ok = snmpm_config:update_usm_user_info(EngineID, UsmUser, priv_key, PrivKey2),
+
+ ?IPRINT("verify updated user user config after update 1"),
+ ?line {ok, AuthProto} = snmpm_config:usm_user_info(EngineID, UsmUser, auth),
+ ?line {ok, AuthKey} = snmpm_config:usm_user_info(EngineID, UsmUser, auth_key),
+ ?line {ok, PrivProto2} = snmpm_config:usm_user_info(EngineID, UsmUser, priv),
+ ?line {ok, PrivKey2} = snmpm_config:usm_user_info(EngineID, UsmUser, priv_key),
+
+ ?IPRINT("usm user update 2"),
+ PrivProto3 = PrivProto1,
+ ok = snmpm_config:update_usm_user_info(EngineID, UsmUser, priv, PrivProto3),
+
+ ?IPRINT("verify updated user user config after update 2"),
+ ?line {ok, AuthProto} = snmpm_config:usm_user_info(EngineID, UsmUser, auth),
+ ?line {ok, AuthKey} = snmpm_config:usm_user_info(EngineID, UsmUser, auth_key),
+ ?line {ok, PrivProto3} = snmpm_config:usm_user_info(EngineID, UsmUser, priv),
+
+ ?IPRINT("Stop config server"),
+ ?line ok = snmpm_config:stop(),
+
+ ?IPRINT("done"),
+ ok.
%%
diff --git a/lib/snmp/test/snmp_otp16649_user.erl b/lib/snmp/test/snmp_otp16649_user.erl
new file mode 100644
index 0000000000..260099a999
--- /dev/null
+++ b/lib/snmp/test/snmp_otp16649_user.erl
@@ -0,0 +1,93 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2020-2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%----------------------------------------------------------------------
+%% Purpose: Utility functions for the (snmp manager) user test(s).
+%%----------------------------------------------------------------------
+
+-module(snmp_otp16649_user).
+
+-behaviour(snmpm_user).
+
+
+%%----------------------------------------------------------------------
+%% Include files
+%%----------------------------------------------------------------------
+-include_lib("common_test/include/ct.hrl").
+-include("snmp_test_lib.hrl").
+
+
+%%----------------------------------------------------------------------
+%% External exports
+%%----------------------------------------------------------------------
+-export([
+ ]).
+
+
+%%----------------------------------------------------------------------
+%% Internal exports
+%%----------------------------------------------------------------------
+
+-export([
+ handle_error/3,
+ handle_agent/5,
+ handle_pdu/4,
+ handle_trap/3,
+ handle_inform/3,
+ handle_report/3
+ ]).
+
+
+%%----------------------------------------------------------------------
+%% User callback functions:
+%%----------------------------------------------------------------------
+
+handle_error(ReqId, Reason, UserPid) ->
+ UserPid ! {handle_error, self(), ReqId, Reason},
+ ignore.
+
+
+handle_agent(Addr, Port, SnmpInfo, UserPid, UserData) ->
+ UserPid ! {handle_agent, self(), Addr, Port, SnmpInfo, UserData},
+ ignore.
+
+
+handle_pdu(TargetName, ReqId, SnmpResponse, UserPid) ->
+ UserPid ! {handle_pdu, self(), TargetName, ReqId, SnmpResponse},
+ ignore.
+
+handle_trap(TargetName, SnmpTrap, UserPid) ->
+ UserPid ! {handle_trap, self(), TargetName, SnmpTrap},
+ ignore.
+
+handle_inform(TargetName, SnmpInform, UserPid) ->
+ UserPid ! {handle_inform, self(), TargetName, SnmpInform},
+ receive
+ {handle_inform_no_response, TargetName} ->
+ no_reply;
+ {handle_inform_response, TargetName} ->
+ ignore
+ end.
+
+handle_report(TargetName, SnmpReport, UserPid) ->
+ UserPid ! {handle_report, self(), TargetName, SnmpReport},
+ ignore.
+
+
diff --git a/lib/snmp/test/snmp_test_lib.erl b/lib/snmp/test/snmp_test_lib.erl
index 41330cfb78..d4e7c53e0a 100644
--- a/lib/snmp/test/snmp_test_lib.erl
+++ b/lib/snmp/test/snmp_test_lib.erl
@@ -39,7 +39,7 @@
replace_config/3, set_config/3, get_config/2, get_config/3]).
-export([fail/3, skip/3]).
-export([hours/1, minutes/1, seconds/1, sleep/1]).
--export([flush_mqueue/0, trap_exit/0, trap_exit/1]).
+-export([flush_mqueue/0, mqueue/0, mqueue/1, trap_exit/0, trap_exit/1]).
-export([ping/1, local_nodes/0, nodes_on/1]).
-export([start_node/2, stop_node/1]).
-export([is_app_running/1,
@@ -976,11 +976,13 @@ analyze_and_print_linux_host_info(Version) ->
%% Check if we need to adjust the factor because of the memory
try linux_which_meminfo() of
AddFactor ->
- io:format("TS Scale Factor: ~w~n", [timetrap_scale_factor()]),
+ io:format("TS Scale Factor: ~w (~w + ~w)~n",
+ [timetrap_scale_factor(), Factor, AddFactor]),
{Factor + AddFactor, []}
catch
_:_:_ ->
- io:format("TS Scale Factor: ~w~n", [timetrap_scale_factor()]),
+ io:format("TS Scale Factor: ~w (~w)~n",
+ [timetrap_scale_factor(), Factor]),
{Factor, []}
end.
@@ -1017,6 +1019,18 @@ linux_cpuinfo_bogomips() ->
"-"
end.
+linux_cpuinfo_BogoMIPS() ->
+ case linux_cpuinfo_lookup("BogoMIPS") of
+ BMips when is_list(BMips) ->
+ try lists:sum([bogomips_to_int(BM) || BM <- BMips])
+ catch
+ _:_:_ ->
+ "-"
+ end;
+ _ ->
+ "-"
+ end.
+
linux_cpuinfo_total_bogomips() ->
case linux_cpuinfo_lookup("total bogomips") of
[TBM] ->
@@ -1046,9 +1060,9 @@ bogomips_to_int(BM) ->
linux_cpuinfo_model() ->
case linux_cpuinfo_lookup("model") of
- [M] ->
+ [M|_] ->
M;
- _ ->
+ _X ->
"-"
end.
@@ -1062,8 +1076,8 @@ linux_cpuinfo_platform() ->
linux_cpuinfo_model_name() ->
case linux_cpuinfo_lookup("model name") of
- [P|_] ->
- P;
+ [M|_] ->
+ M;
_ ->
"-"
end.
@@ -1113,21 +1127,34 @@ linux_which_cpuinfo(yellow_dog) ->
{ok, CPU};
linux_which_cpuinfo(wind_river) ->
- CPU =
+ Model =
case linux_cpuinfo_model() of
"-" ->
- throw(noinfo);
- Model ->
- case linux_cpuinfo_platform() of
+ %% Try 'model name'
+ case linux_cpuinfo_model_name() of
"-" ->
- Model;
- Platform ->
- Model ++ " (" ++ Platform ++ ")"
- end
+ throw(noinfo);
+ MN ->
+ MN
+ end;
+ M ->
+ M
+ end,
+ CPU =
+ case linux_cpuinfo_platform() of
+ "-" ->
+ Model;
+ Platform ->
+ Model ++ " (" ++ Platform ++ ")"
end,
case linux_cpuinfo_total_bogomips() of
"-" ->
- {ok, CPU};
+ case linux_cpuinfo_BogoMIPS() of
+ "-" ->
+ {ok, CPU};
+ BMips ->
+ {ok, {CPU, BMips}}
+ end;
BMips ->
{ok, {CPU, BMips}}
end;
@@ -2157,6 +2184,20 @@ num_schedulers_to_factor() ->
linux_info_lookup(Key, File) ->
+ %% try
+ %% begin
+ %% GREP = os:cmd("grep " ++ "\"" ++ Key ++ "\"" ++ " " ++ File),
+ %% io:format("linux_info_lookup() -> GREP: ~p~n", [GREP]),
+ %% TOKENS = string:tokens(GREP, [$:,$\n]),
+ %% io:format("linux_info_lookup() -> TOKENS: ~p~n", [TOKENS]),
+ %% INFO = [string:trim(S) || S <- TOKENS],
+ %% io:format("linux_info_lookup() -> INFO: ~p~n", [INFO]),
+ %% linux_info_lookup_collect(Key, INFO, [])
+ %% end
+ %% catch
+ %% _:_:_ ->
+ %% "-"
+ %% end.
try [string:trim(S) || S <- string:tokens(os:cmd("grep " ++ "\"" ++ Key ++ "\"" ++ " " ++ File), [$:,$\n])] of
Info ->
linux_info_lookup_collect(Key, Info, [])
@@ -2200,6 +2241,17 @@ sleep(MSecs) ->
%% Process utility function
%%
+mqueue() ->
+ mqueue(self()).
+mqueue(Pid) when is_pid(Pid) ->
+ Key = messages,
+ case process_info(Pid, Key) of
+ {Key, Msgs} ->
+ Msgs;
+ _ ->
+ []
+ end.
+
flush_mqueue() ->
io:format("~p~n", [lists:reverse(flush_mqueue([]))]).
diff --git a/lib/snmp/test/snmp_test_lib.hrl b/lib/snmp/test/snmp_test_lib.hrl
index a853d3cc09..78d1453c12 100644
--- a/lib/snmp/test/snmp_test_lib.hrl
+++ b/lib/snmp/test/snmp_test_lib.hrl
@@ -85,6 +85,8 @@
%% - Process utility macros -
-define(FLUSH(), ?LIB:flush_mqueue()).
+-define(MQUEUE(), ?LIB:mqueue()).
+-define(MQUEUE(P), ?LIB:mqueue(P)).
-define(ETRAP_GET(), ?LIB:trap_exit()).
-define(ETRAP_SET(O), ?LIB:trap_exit(O)).
-define(PINFO(__P__), try process_info(__P__)
diff --git a/lib/snmp/test/snmp_test_mgr.erl b/lib/snmp/test/snmp_test_mgr.erl
index f8eb85734e..d7db3a2b0d 100644
--- a/lib/snmp/test/snmp_test_mgr.erl
+++ b/lib/snmp/test/snmp_test_mgr.erl
@@ -312,6 +312,9 @@ is_options_ok([{ipfamily,IpFamily}|Opts])
is_options_ok(Opts);
is_options_ok([{agent_udp,Int}|Opts]) when is_integer(Int) ->
is_options_ok(Opts);
+is_options_ok([{agent_udp, {IntR, IntT}}|Opts]) when is_integer(IntR) andalso
+ is_integer(IntT) ->
+ is_options_ok(Opts);
is_options_ok([{trap_udp,Int}|Opts]) when is_integer(Int) ->
is_options_ok(Opts);
is_options_ok([{community,List}|Opts]) when is_list(List) ->
@@ -429,8 +432,8 @@ handle_cast(iter_get_next, State)
{noreply, execute_request(get_next, Oids, State)};
handle_cast(iter_get_next, State) ->
- ?PACK_SERV:error("[Iterated get-next] No Response PDU to "
- "start iterating from.", []),
+ ?EPRINT("[Iterated get-next] No Response PDU to "
+ "start iterating from.", []),
{noreply, State};
handle_cast({iter_get_next, N}, State) ->
@@ -442,8 +445,8 @@ handle_cast({iter_get_next, N}, State) ->
State#state.packet_server),
{noreply, State#state{last_received_pdu = PDU}};
true ->
- ?PACK_SERV:error("[Iterated get-next] No Response PDU to "
- "start iterating from.", []),
+ ?EPRINT("[Iterated get-next] No Response PDU to "
+ "start iterating from.", []),
{noreply, State}
end;
@@ -537,7 +540,7 @@ report_error(#state{quiet = true, parent = Pid}, Format, Args) ->
Reason = lists:flatten(io_lib:format(Format, Args)),
Pid ! {oid_error, Reason};
report_error(_, Format, Args) ->
- ?PACK_SERV:error(Format, Args).
+ ?EPRINT(Format, Args).
get_oid_from_varbind(#varbind{oid = Oid}) -> Oid.
@@ -699,13 +702,13 @@ echo_pdu(PDU, MiniMIB) ->
%% Test Sequence
%%----------------------------------------------------------------------
echo_errors({error, Id, {ExpectedFormat, ExpectedData}, {Format, Data}})->
- ?IPRINT("*** Unexpected Behaviour *** Id: ~w.~n"
- " Expected: " ++ ExpectedFormat ++ "~n"
- " Got: " ++ Format ++ "~n",
+ ?EPRINT("*** Unexpected Behaviour *** Id: ~w"
+ "~n Expected: " ++ ExpectedFormat ++
+ "~n Got: " ++ Format ++ "~n",
[Id] ++ ExpectedData ++ Data),
{error, Id, {ExpectedFormat, ExpectedData}, {Format, Data}};
echo_errors(ok) -> ok;
-echo_errors({ok, Val}) -> {ok, Val}.
+echo_errors({ok, _} = OK) -> OK.
get_response_impl(Id, ExpVars) ->
?IPRINT("await response ~w with"
@@ -738,9 +741,12 @@ get_response_impl(Id, ExpVars) ->
{"Type: ~w, ErrStat: ~w, Idx: ~w",
[Type2, Err2, Index2]}};
- {error, Reason} ->
+ {error, Reason} ->
+ %% We did not get the message we wanted,
+ %% but what did we get?
?EPRINT("unexpected receive pdu error: ~w"
- "~n ~p", [Id, Reason]),
+ "~n Reason: ~p"
+ "~n Msg Que: ~p", [Id, Reason, ?MQUEUE()]),
format_reason(Id, Reason)
end.
@@ -756,8 +762,11 @@ expect_impl(Id, any) ->
?IPRINT("received expected pdu (~w)", [Id]),
ok;
{error, Reason} ->
+ %% We did not get the message we wanted,
+ %% but what did we get?
?EPRINT("unexpected receive error: ~w"
- "~n ~p", [Id, Reason]),
+ "~n Reason: ~p"
+ "~n Msg Que: ~p", [Id, Reason, ?MQUEUE()]),
format_reason(Id, Reason)
end;
@@ -768,8 +777,11 @@ expect_impl(Id, return) ->
?IPRINT("received expected pdu (~w)", [Id]),
{ok, PDU};
{error, Reason} ->
+ %% We did not get the message we wanted,
+ %% but what did we get?
?EPRINT("unexpected receive error: ~w"
- "~n ~p", [Id, Reason]),
+ "~n Reason: ~p"
+ "~n Msg Que: ~p", [Id, Reason, ?MQUEUE()]),
format_reason(Id, Reason)
end;
@@ -780,8 +792,11 @@ expect_impl(Id, trap) ->
?IPRINT("received expected trap (~w)", [Id]),
ok;
{error, Reason} ->
+ %% We did not get the message we wanted,
+ %% but what did we get?
?EPRINT("unexpected receive error: ~w"
- "~n ~p", [Id, Reason]),
+ "~n Reason: ~p"
+ "~n Msg Que: ~p", [Id, Reason, ?MQUEUE()]),
format_reason(Id, Reason)
end;
@@ -818,8 +833,11 @@ expect_impl(Id, Err) when is_atom(Err) ->
{"ErrorStatus: ~w", [Err2]}};
{error, Reason} ->
+ %% We did not get the message we wanted,
+ %% but what did we get?
?EPRINT("unexpected receive error: ~w"
- "~n ~p", [Id, Reason]),
+ "~n Reason: ~p"
+ "~n Msg Que: ~p", [Id, Reason, ?MQUEUE()]),
format_reason(Id, Reason)
end;
@@ -852,8 +870,11 @@ expect_impl(Id, ExpectedVarbinds) when is_list(ExpectedVarbinds) ->
{"Type: ~w, ErrStat: ~w, Idx: ~w", [Type2, Err2, Index2]}};
{error, Reason} ->
+ %% We did not get the message we wanted,
+ %% but what did we get?
?EPRINT("unexpected receive error: ~w"
- "~n ~p", [Id, Reason]),
+ "~n Reason: ~p"
+ "~n Msg Que: ~p", [Id, Reason, ?MQUEUE()]),
format_reason(Id, Reason)
end.
@@ -886,8 +907,11 @@ expect_impl(Id, v2trap, ExpectedVarbinds) when is_list(ExpectedVarbinds) ->
{"Type: ~w, ErrStat: ~w, Idx: ~w", [Type2, Err2, Index2]}};
{error, Reason} ->
+ %% We did not get the message we wanted,
+ %% but what did we get?
?EPRINT("unexpected receive error: ~w"
- "~n ~p", [Id, Reason]),
+ "~n Reason: ~p"
+ "~n Msg Que: ~p", [Id, Reason, ?MQUEUE()]),
format_reason(Id, Reason)
end;
@@ -920,8 +944,11 @@ expect_impl(Id, report, ExpectedVarbinds) when is_list(ExpectedVarbinds) ->
{"Type: ~w, ErrStat: ~w, Idx: ~w", [Type2, Err2, Index2]}};
{error, Reason} ->
+ %% We did not get the message we wanted,
+ %% but what did we get?
?EPRINT("unexpected receive error: ~w"
- "~n ~p", [Id, Reason]),
+ "~n Reason: ~p"
+ "~n Msg Que: ~p", [Id, Reason, ?MQUEUE()]),
format_reason(Id, Reason)
end;
@@ -981,8 +1008,11 @@ expect_impl(Id, {inform, Reply}, ExpectedVarbinds)
{"Type: ~w, ErrStat: ~w, Idx: ~w", [Type2, Err2, Index2]}};
{error, Reason} ->
+ %% We did not get the message we wanted,
+ %% but what did we get?
?EPRINT("unexpected receive error: ~w"
- "~n ~p", [Id, Reason]),
+ "~n Reason: ~p"
+ "~n Msg Que: ~p", [Id, Reason, ?MQUEUE()]),
format_reason(Id, Reason)
end.
@@ -1040,8 +1070,11 @@ expect_impl(Id, Err, Index, any = _ExpectedVarbinds) ->
{"Type: ~w, ErrStat: ~w, Idx: ~w", [Type2, Err2, Index2]}};
{error, Reason} ->
+ %% We did not get the message we wanted,
+ %% but what did we get?
?EPRINT("unexpected (receive) response: "
- "~n ~p", [Reason]),
+ "~n Reason: ~p"
+ "~n Msg Que: ~p", [Id, Reason, ?MQUEUE()]),
format_reason(Id, Reason)
end;
@@ -1119,8 +1152,11 @@ expect_impl(Id, Err, Index, ExpectedVarbinds) ->
[Type2,Err2,Index2,VBs]}};
{error, Reason} ->
+ %% We did not get the message we wanted,
+ %% but what did we get?
?EPRINT("unexpected receive pdu error: ~w"
- "~n ~p", [Id, Reason]),
+ "~n Reason: ~p"
+ "~n Msg Que: ~p", [Id, Reason, ?MQUEUE()]),
format_reason(Id, Reason)
end.
@@ -1169,8 +1205,11 @@ expect_impl(Id, trap, Enterp, Generic, Specific, ExpectedVarbinds) ->
[Ent2, G2, Spec2, VBs]}};
{error, Reason} ->
+ %% We did not get the message we wanted,
+ %% but what did we get?
?EPRINT("unexpected receive trap pdu error: ~w"
- "~n ~p", [Id, Reason]),
+ "~n Reason: ~p"
+ "~n Msg Que: ~p", [Id, Reason, ?MQUEUE()]),
format_reason(Id, Reason)
end.
diff --git a/lib/snmp/test/snmp_test_mgr_misc.erl b/lib/snmp/test/snmp_test_mgr_misc.erl
index b1b1b8040b..2a20ebee42 100644
--- a/lib/snmp/test/snmp_test_mgr_misc.erl
+++ b/lib/snmp/test/snmp_test_mgr_misc.erl
@@ -28,7 +28,6 @@
stop/1,
send_discovery_pdu/2,
send_pdu/2, send_msg/4, send_bytes/2,
- error/2,
get_pdu/1, set_pdu/2, format_hdr/1]).
%% internal exports
@@ -62,9 +61,29 @@ start_link_packet(
start_link_packet(
InHandler, AgentIp, UdpPort, TrapUdp, VsnHdr, Version, Dir, BufSz,
Dbg, IpFamily) when is_integer(UdpPort) ->
+ do_start_link_packet(InHandler,
+ AgentIp, UdpPort, TrapUdp,
+ VsnHdr, Version, Dir, BufSz,
+ Dbg, IpFamily);
+start_link_packet(InHandler,
+ AgentIp, {AReqPort, ATrapPort} = UdpPorts, TrapUdp,
+ VsnHdr, Version, Dir, BufSz,
+ Dbg, IpFamily) when is_integer(AReqPort) andalso
+ is_integer(ATrapPort) ->
+ do_start_link_packet(InHandler,
+ AgentIp, UdpPorts, TrapUdp,
+ VsnHdr, Version, Dir, BufSz,
+ Dbg, IpFamily).
+
+do_start_link_packet(InHandler,
+ AgentIp, UdpPorts, TrapUdp,
+ VsnHdr, Version, Dir, BufSz,
+ Dbg, IpFamily) ->
Args =
[self(),
- InHandler, AgentIp, UdpPort, TrapUdp, VsnHdr, Version, Dir, BufSz,
+ InHandler,
+ AgentIp, UdpPorts, TrapUdp,
+ VsnHdr, Version, Dir, BufSz,
Dbg, IpFamily],
proc_lib:start_link(?MODULE, init_packet, Args).
@@ -100,7 +119,7 @@ send_bytes(Bytes, PacketPid) ->
%%--------------------------------------------------
init_packet(
Parent,
- SnmpMgr, AgentIp, UdpPort, TrapUdp, VsnHdr, Version, Dir, BufSz,
+ SnmpMgr, AgentIp, UdpPorts, TrapUdp, VsnHdr, Version, Dir, BufSz,
DbgOptions, IpFamily) ->
%% This causes "verbosity printouts" to print (from the
%% specified level) in the modules we "borrow" from the
@@ -117,7 +136,7 @@ init_packet(
init_usm(Version, Dir),
proc_lib:init_ack(Parent, self()),
?IPRINT("started"),
- packet_loop(SnmpMgr, UdpId, AgentIp, UdpPort, VsnHdr, Version, []).
+ packet_loop(SnmpMgr, UdpId, AgentIp, UdpPorts, VsnHdr, Version, []).
init_debug(Dbg) when is_atom(Dbg) ->
put(debug,Dbg),
@@ -139,7 +158,7 @@ init_debug(DbgOptions) when is_list(DbgOptions) ->
ok.
-packet_loop(SnmpMgr, UdpId, AgentIp, UdpPort, VsnHdr, Version, MsgData) ->
+packet_loop(SnmpMgr, UdpId, AgentIp, UdpPorts, VsnHdr, Version, MsgData) ->
receive
{send_discovery_pdu, From, Pdu} ->
d("packet_loop -> received send_discovery_pdu with"
@@ -151,9 +170,10 @@ packet_loop(SnmpMgr, UdpId, AgentIp, UdpPort, VsnHdr, Version, MsgData) ->
{M, B} when is_list(B) ->
put(discovery, {M, From}),
display_outgoing_message(M),
- udp_send(UdpId, AgentIp, UdpPort, B)
+ Port = select_request_port(UdpPorts),
+ udp_send(UdpId, AgentIp, Port, B)
end,
- packet_loop(SnmpMgr, UdpId, AgentIp, UdpPort, VsnHdr, Version, []);
+ packet_loop(SnmpMgr, UdpId, AgentIp, UdpPorts, VsnHdr, Version, []);
{send_pdu, Pdu} ->
d("packet_loop -> received send_pdu with"
@@ -161,10 +181,11 @@ packet_loop(SnmpMgr, UdpId, AgentIp, UdpPort, VsnHdr, Version, MsgData) ->
case mk_msg(Version, Pdu, VsnHdr, MsgData) of
error ->
ok;
- B when is_list(B) ->
- udp_send(UdpId, AgentIp, UdpPort, B)
+ B when is_list(B) ->
+ Port = select_request_port(UdpPorts),
+ udp_send(UdpId, AgentIp, Port, B)
end,
- packet_loop(SnmpMgr,UdpId,AgentIp,UdpPort,VsnHdr,Version,[]);
+ packet_loop(SnmpMgr, UdpId, AgentIp, UdpPorts, VsnHdr, Version, []);
{send_msg, Msg, Ip, Udp} ->
d("packet_loop -> received send_msg with"
@@ -173,44 +194,70 @@ packet_loop(SnmpMgr, UdpId, AgentIp, UdpPort, VsnHdr, Version, MsgData) ->
"~n Udp: ~p", [Msg,Ip,Udp]),
case catch snmp_pdus:enc_message(Msg) of
{'EXIT', Reason} ->
- error("Encoding error:"
- "~n Msg: ~w"
- "~n Reason: ~w",[Msg, Reason]);
+ ?EPRINT("Encoding error:"
+ "~n Msg: ~w"
+ "~n Reason: ~w",[Msg, Reason]);
B when is_list(B) ->
udp_send(UdpId, Ip, Udp, B)
end,
- packet_loop(SnmpMgr,UdpId,AgentIp,UdpPort,VsnHdr,Version,[]);
- {udp, UdpId, Ip, UdpPort, Bytes} ->
+ packet_loop(SnmpMgr, UdpId, AgentIp, UdpPorts, VsnHdr, Version, []);
+
+ {udp, UdpId, Ip, Port, Bytes} ->
d("packet_loop -> received udp with"
"~n UdpId: ~p"
"~n Ip: ~p"
- "~n UdpPort: ~p"
- "~n sz(Bytes): ~p", [UdpId, Ip, UdpPort, sz(Bytes)]),
+ "~n Port: ~p (~p)"
+ "~n sz(Bytes): ~p", [UdpId, Ip, Port, UdpPorts, sz(Bytes)]),
+ case UdpPorts of
+ Port ->
+ ok;
+ {Port, _} -> % Should be a (request) response
+ ok;
+ {_, Port} -> % Should be a trap
+ ok;
+ _ ->
+ d("packet_loop -> received packet from unknown port"
+ "~n ~p", [Port]),
+ exit({snmp_packet_from_unknown_port, Port, UdpPorts})
+ end,
MsgData3 = handle_udp_packet(Version, erase(discovery),
- UdpId, Ip, UdpPort, Bytes,
+ UdpId, Ip, Port, Bytes,
SnmpMgr, AgentIp),
- packet_loop(SnmpMgr,UdpId,AgentIp,UdpPort,VsnHdr,Version,
+ packet_loop(SnmpMgr, UdpId, AgentIp, UdpPorts, VsnHdr, Version,
MsgData3);
+
{udp_error, UdpId, Reason} ->
gen_udp:close(UdpId),
exit({udp_error, Reason});
{send_bytes, B} ->
d("packet_loop -> received send_bytes with"
- "~n sz(B): ~p", [sz(B)]),
- udp_send(UdpId, AgentIp, UdpPort, B),
- packet_loop(SnmpMgr,UdpId,AgentIp,UdpPort,VsnHdr,Version,[]);
+ "~n sz(B): ~p", [sz(B)]),
+ Port = select_request_port(UdpPorts),
+ udp_send(UdpId, AgentIp, Port, B),
+ packet_loop(SnmpMgr, UdpId, AgentIp, UdpPorts, VsnHdr, Version, []);
+
{stop, Pid} ->
d("packet_loop -> received stop from ~p", [Pid]),
gen_udp:close(UdpId),
Pid ! {self(), stopped},
exit(normal);
+
Other ->
d("packet_loop -> received unknown"
"~n ~p", [Other]),
exit({snmp_packet_got_other, Other})
end.
+select_request_port({Port, _}) when is_integer(Port) ->
+ Port;
+select_request_port(Port) when is_integer(Port) ->
+ Port.
+
+%% select_trap_port({_, Port}) when is_integer(Port) ->
+%% Port;
+%% select_trap_port(Port) when is_integer(Port) ->
+%% Port.
handle_udp_packet(_V, undefined,
UdpId, Ip, UdpPort,
@@ -228,9 +275,13 @@ handle_udp_packet(_V, undefined,
catch
Class:Error:_ ->
- error("Decoding error (~w). Bytes: ~w ~n Error: ~w "
- "(UDPport: ~w, Ip: ~w)",
- [Class, Bytes, Error, UdpPort, Ip]),
+ ?EPRINT("Decoding error: "
+ "~n Class: ~w"
+ "~n Error: ~p"
+ "~n Bytes: ~p"
+ "~n Port: ~w"
+ "~n Ip: ~p)",
+ [Class, Error, Bytes, UdpPort, Ip]),
[]
end;
handle_udp_packet(V, {DiscoReqMsg, From}, _UdpId, _Ip, _UdpPort,
@@ -271,26 +322,30 @@ handle_v3_message(Mgr, UdpId, Ip, UdpPort, AgentIp,
catch
throw:{error, Reason, B}:_ ->
udp_send(UdpId, AgentIp, UdpPort, B),
- error("Decoding (v3) error. Auto-sending Report.\n"
- "~n Reason: ~w "
- "(UDPport: ~w, Ip: ~w)",
- [Reason, UdpPort, Ip]),
+ ?EPRINT("Decoding (v3) error - Auto-sending Report:"
+ "~n Reason: ~p"
+ "~n Port: ~p"
+ "~n Ip: ~p",
+ [Reason, UdpPort, Ip]),
[];
throw:{error, Reason}:_ ->
- error("Decoding (v3) error. "
- "~n Bytes: ~w"
- "~n Reason: ~w "
- "(UDPport: ~w, Ip: ~w)",
- [Bytes, Reason, UdpPort, Ip]),
+ ?EPRINT("Decoding (v3) error:"
+ "~n Reason: ~p"
+ "~n Bytes: ~p"
+ "~n Port: ~p"
+ "~n Ip: ~p",
+ [Reason, Bytes, UdpPort, Ip]),
[];
Class:Error:_ ->
- error("Decoding (v3) error (~w). "
- "~n Bytes: ~w"
- "~n Error: ~w "
- "(UDPport: ~w, Ip: ~w)",
- [Class, Bytes, Error, UdpPort, Ip]),
+ ?EPRINT("Decoding (v3) error:"
+ "~n Class: ~p"
+ "~n Error: ~p"
+ "~n Bytes: ~p"
+ "~n Port: ~p"
+ "~n Ip: ~p",
+ [Class, Error, Bytes, UdpPort, Ip]),
[]
end.
@@ -325,11 +380,13 @@ handle_v1_or_v2_message(Mgr, _UdpId, Ip, UdpPort, _AgentIp,
catch
Class:Error:_ ->
- error("Decoding (v1 or v2) error (~w): "
- "~n Bytes: ~w"
- "~n Error: ~w "
- "(UDPport: ~w, Ip: ~w)",
- [Class, Bytes, Error, UdpPort, Ip])
+ ?EPRINT("Decoding (v1 or v2) error: "
+ "~n Class: ~p"
+ "~n Error: ~p "
+ "~n Bytes: ~p"
+ "~n Port: ~p"
+ "~n Ip: ~p",
+ [Class, Error, Bytes, UdpPort, Ip])
end.
@@ -462,12 +519,6 @@ generate_v3_report_msg(_MsgID, _MsgSecurityModel, Data, ErrorInfo) ->
undefined).
-error(Format, Data) ->
- io:format("*** Error ***~n"),
- ok = io:format(Format, Data),
- io:format("~n").
-
-
mk_discovery_msg('version-3', Pdu, _VsnHdr, UserName) ->
ScopedPDU = #scopedPdu{contextEngineID = "",
contextName = "",
@@ -492,9 +543,9 @@ mk_discovery_msg('version-3', Pdu, _VsnHdr, UserName) ->
Msg = #message{version = 'version-3', vsn_hdr = Hdr, data = Bytes},
case (catch snmp_pdus:enc_message_only(Msg)) of
{'EXIT', Reason} ->
- error("Discovery encoding error: "
- "~n Pdu: ~w"
- "~n Reason: ~w",[Pdu, Reason]),
+ ?EPRINT("Discovery encoding error: "
+ "~n Pdu: ~p"
+ "~n Reason: ~p", [Pdu, Reason]),
error;
L when is_list(L) ->
{Msg#message{data = ScopedPDU}, L}
@@ -503,9 +554,9 @@ mk_discovery_msg(Version, Pdu, {Com, _, _, _, _}, _UserName) ->
Msg = #message{version = Version, vsn_hdr = Com, data = Pdu},
case catch snmp_pdus:enc_message(Msg) of
{'EXIT', Reason} ->
- error("Discovery encoding error:"
- "~n Pdu: ~w"
- "~n Reason: ~w",[Pdu, Reason]),
+ ?EPRINT("Discovery encoding error:"
+ "~n Pdu: ~p"
+ "~n Reason: ~p", [Pdu, Reason]),
error;
L when is_list(L) ->
{Msg, L}
@@ -551,14 +602,14 @@ mk_msg('version-3', Pdu, {Context, User, EngineID, CtxEngineId, SecLevel},
case catch snmpa_usm:generate_outgoing_msg(Message, SecEngineID,
SecName, SecData, SecLevel) of
{'EXIT', Reason} ->
- error("version-3 message encoding exit"
- "~n Pdu: ~w"
- "~n Reason: ~w",[Pdu, Reason]),
+ ?EPRINT("version-3 message encoding exit"
+ "~n Pdu: ~p"
+ "~n Reason: ~p", [Pdu, Reason]),
error;
{error, Reason} ->
- error("version-3 message encoding error"
- "~n Pdu: ~w"
- "~n Reason: ~w",[Pdu, Reason]),
+ ?EPRINT("version-3 message encoding error"
+ "~n Pdu: ~p"
+ "~n Reason: ~p", [Pdu, Reason]),
error;
Packet ->
Packet
@@ -567,9 +618,9 @@ mk_msg(Version, Pdu, {Com, _User, _EngineID, _Ctx, _SecLevel}, _SecData) ->
Msg = #message{version = Version, vsn_hdr = Com, data = Pdu},
case catch snmp_pdus:enc_message(Msg) of
{'EXIT', Reason} ->
- error("~w encoding error"
- "~n Pdu: ~w"
- "~n Reason: ~w",[Version, Pdu, Reason]),
+ ?EPRINT("~w encoding error"
+ "~n Pdu: ~p"
+ "~n Reason: ~p", [Version, Pdu, Reason]),
error;
B when is_list(B) ->
B
@@ -590,12 +641,14 @@ vsn('version-2') -> v2c.
udp_send(UdpId, AgentIp, UdpPort, B) ->
?vlog("attempt send message (~w bytes) to ~p", [sz(B), {AgentIp, UdpPort}]),
case (catch gen_udp:send(UdpId, AgentIp, UdpPort, B)) of
- {error,ErrorReason} ->
- error("failed (error) sending message to ~p:~p: "
- "~n ~p",[AgentIp, UdpPort, ErrorReason]);
- {'EXIT',ExitReason} ->
- error("failed (exit) sending message to ~p:~p:"
- "~n ~p",[AgentIp, UdpPort, ExitReason]);
+ {error, ErrorReason} ->
+ ?EPRINT("failed (error) sending message to ~p:~p: "
+ "~n ~p",[AgentIp, UdpPort, ErrorReason]),
+ error;
+ {'EXIT', ExitReason} ->
+ ?EPRINT("failed (exit) sending message to ~p:~p:"
+ "~n ~p",[AgentIp, UdpPort, ExitReason]),
+ error;
_ ->
ok
end.
diff --git a/lib/snmp/vsn.mk b/lib/snmp/vsn.mk
index bc1ec4beb0..e1e018878a 100644
--- a/lib/snmp/vsn.mk
+++ b/lib/snmp/vsn.mk
@@ -19,6 +19,6 @@
# %CopyrightEnd%
APPLICATION = snmp
-SNMP_VSN = 5.6.1
+SNMP_VSN = 5.7.3
PRE_VSN =
APP_VSN = "$(APPLICATION)-$(SNMP_VSN)$(PRE_VSN)"
diff --git a/lib/ssh/doc/src/hardening.xml b/lib/ssh/doc/src/hardening.xml
index 5d65f5da3f..c1d3f7669c 100644
--- a/lib/ssh/doc/src/hardening.xml
+++ b/lib/ssh/doc/src/hardening.xml
@@ -144,6 +144,43 @@
</section>
<section>
+ <title>Verifying the remote client in a daemon (server)</title>
+ <taglist>
+ <tag>Password checking</tag>
+ <item>
+ <p>The default password checking is with the list in the
+ <seeerl marker="ssh#option-user_passwords">user_passwords</seeerl> option in the SSH daemon.
+ It could be replaced with a <seeerl marker="ssh#option-pwdfun">pwdfun</seeerl> plugin. The
+ arity four variant (<seetype marker="ssh#pwdfun_4"><c>pwdfun_4()</c></seetype>)
+ can also be used for introducing delays after failed password checking attempts. Here is a simple
+ example of such a pwdfun:
+ </p>
+ <code>
+fun(User, Password, _PeerAddress, State) ->
+ case lists:member({User,Password}, my_user_pwds()) of
+ true ->
+ {true, undefined}; % Reset delay time
+ false when State == undefined ->
+ timer:sleep(1000),
+ {false, 2000}; % Next delay is 2000 ms
+ false when is_integer(State) ->
+ timer:sleep(State),
+ {false, 2*State} % Double the delay for each failure
+ end
+end.
+</code>
+ <p>If a public key is used for logging in, there is normally no checking of the user name. It
+ could be enabled by setting the option
+ <seeerl marker="ssh#option-pk_check_user"><c>pk_check_user</c></seeerl>
+ to <c>true</c>.
+ In that case the pwdfun will get the atom <c>pubkey</c> in the password argument.
+ </p>
+ </item>
+
+ </taglist>
+ </section>
+
+ <section>
<title>Hardening in the cryptographic area</title>
<section>
<title>Algorithm selection</title>
diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml
index 9d07f88a44..57a1d5f9c7 100644
--- a/lib/ssh/doc/src/notes.xml
+++ b/lib/ssh/doc/src/notes.xml
@@ -30,6 +30,78 @@
<file>notes.xml</file>
</header>
+<section><title>Ssh 4.10.7</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The SSH daemon erroneously replaced LF with CRLF also
+ when there was no pty requested from the server.</p>
+ <p>
+ Own Id: OTP-17108 Aux Id: ERL-1442 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.10.6</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed problems in the ssh cli/shell handling. Most
+ important are:</p>
+ <p>
+ 1) the ssh:shell function did sometimes cause the input
+ to be echoed twice,</p>
+ <p>
+ 2) the ssh:shell function didn't transfer the LANG and
+ LC_ALL shell variables to the connected server which
+ sometimes made Unicode handling erroneous,</p>
+ <p>
+ 3) Unicode was not always transferred correctly to and
+ from the peer.</p>
+ <p>
+ Own Id: OTP-16799</p>
+ </item>
+ <item>
+ <p>
+ The SSH protocol message SSH_MSG_DISCONNECT was sometimes
+ sent instead of SSH_MSG_CHANNEL_FAILURE</p>
+ <p>
+ Own Id: OTP-16900</p>
+ </item>
+ <item>
+ <p>
+ The ssh_cli module now always sends the exit-status to
+ connected clients so they can use that to check for
+ successful command execution.</p>
+ <p>
+ Own Id: OTP-16908 Aux Id: PR-2753 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ A new option <seeerl
+ marker="ssh:ssh#option-pk_check_user"><c>pk_check_user</c></seeerl>
+ enables checking of the client's user name in the server
+ when doing public key authentication.</p>
+ <p>
+ Own Id: OTP-16889</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Ssh 4.10.5</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -398,6 +470,21 @@
</section>
+<section><title>Ssh 4.9.1.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix decoder bug.</p>
+ <p>
+ Own Id: OTP-16904</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Ssh 4.9.1.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -720,6 +807,21 @@
</section>
+<section><title>Ssh 4.7.6.5</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix decoder bug.</p>
+ <p>
+ Own Id: OTP-16904</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Ssh 4.7.6.4</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml
index a0355219a1..b88bdc1667 100644
--- a/lib/ssh/doc/src/ssh.xml
+++ b/lib/ssh/doc/src/ssh.xml
@@ -565,6 +565,26 @@
</p>
</item>
+ <tag><marker id="option-pk_check_user"/><c>pk_check_user</c></tag>
+ <item>
+ <p>Enables checking of the
+ <seetype marker="#authentication_client_options">client's user name</seetype>
+ in the server when doing public key authentication. It is disabled by default.
+ </p>
+ <p>The term "user" is used differently in OpenSSH and SSH in Erlang/OTP:
+ see more in the <seeguide marker="terminology#the-term--user-">User's Guide</seeguide>.
+ </p>
+ <p>If the option is enabled, and no
+ <seeerl marker="#option-pwdfun"><c>pwdfun</c></seeerl>
+ is present, the user name must present in the
+ <seeerl marker="#option-user_passwords">user_passwords</seeerl>
+ for the check to succeed but the value of the password is not checked.
+ </p>
+ <p>In case of a <seeerl marker="#option-pwdfun"><c>pwdfun</c></seeerl>
+ checking the user, the atom <c>pubkey</c> is put in the password argument.
+ </p>
+ </item>
+
<tag><marker id="option-password"/><c>password</c></tag>
<item>
<p>Provides a global password that authenticates any user.</p>
@@ -587,7 +607,6 @@
the <c>State</c> variable could be used. This state is per connection only. The first time the pwdfun
is called for a connection, the <c>State</c> variable has the value <c>undefined</c>.
</p>
-
<p>The fun should return:
</p>
<list type="bulleted">
@@ -598,9 +617,12 @@
<item><c>{true, NewState:any()}</c> if the user and password is valid</item>
<item><c>{false, NewState:any()}</c> if the user or password is invalid</item>
</list>
-
<p>A third usage is to block login attempts from a missbehaving peer. The <c>State</c> described above
can be used for this. The return value <c>disconnect</c> is useful for this.</p>
+ <p>In case of the <seeerl marker="#option-pk_check_user"><c>pk_check_user</c></seeerl> is set,
+ the atom <c>pubkey</c> is put in the password argument when validating a public key login. The
+ pwdfun is then responsible to check that the user name is valid.
+ </p>
</item>
<tag><c>pwdfun</c> with
@@ -613,6 +635,10 @@
<item><c>true</c> if the user and password is valid</item>
<item><c>false</c> if the user or password is invalid</item>
</list>
+ <p>In case of the <seeerl marker="#option-pk_check_user"><c>pk_check_user</c></seeerl> is set,
+ the atom <c>pubkey</c> is put in the password argument when validating a public key login. The
+ pwdfun is then responsible to check that the user name is valid.
+ </p>
<p>This variant is kept for compatibility.</p>
</item>
</taglist>
diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl
index 07053105c1..d4b492d61c 100644
--- a/lib/ssh/src/ssh.erl
+++ b/lib/ssh/src/ssh.erl
@@ -509,7 +509,11 @@ shell(Socket) when is_port(Socket) ->
shell(ConnectionRef) when is_pid(ConnectionRef) ->
case ssh_connection:session_channel(ConnectionRef, infinity) of
{ok,ChannelId} ->
- success = ssh_connection:ptty_alloc(ConnectionRef, ChannelId, []),
+ success = ssh_connection:ptty_alloc(ConnectionRef, ChannelId,
+ [{pty_opts, [{echo,0}]}
+ ]),
+ success = ssh_connection:send_environment_vars(ConnectionRef, ChannelId,
+ ["LANG", "LC_ALL"]),
Args = [{channel_cb, ssh_shell},
{init_args,[ConnectionRef, ChannelId]},
{cm, ConnectionRef}, {channel_id, ChannelId}],
diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl
index 08aef95f72..33f6833830 100644
--- a/lib/ssh/src/ssh.hrl
+++ b/lib/ssh/src/ssh.hrl
@@ -356,6 +356,7 @@
ssh_file:system_dir_daemon_option()
| {auth_method_kb_interactive_data, prompt_texts() }
| {user_passwords, [{UserName::string(),Pwd::string()}]}
+ | {pk_check_user, boolean()}
| {password, string()}
| {pwdfun, pwdfun_2() | pwdfun_4()} .
@@ -369,9 +370,9 @@
-type kb_int_fun_4() :: fun((Peer::ip_port(), User::string(), Service::string(), State::any()) -> kb_int_tuple()).
-type kb_int_tuple() :: {Name::string(), Instruction::string(), Prompt::string(), Echo::boolean()}.
--type pwdfun_2() :: fun((User::string(), Password::string()) -> boolean()) .
+-type pwdfun_2() :: fun((User::string(), Password::string()|pubkey) -> boolean()) .
-type pwdfun_4() :: fun((User::string(),
- Password::string(),
+ Password::string()|pubkey,
PeerAddress::ip_port(),
State::any()) ->
boolean() | disconnect | {boolean(),NewState::any()}
@@ -503,7 +504,8 @@
recv_ext_info
}).
--record(ssh_pty, {term = "", % e.g. "xterm"
+-record(ssh_pty, {c_version = "", % client version string, e.g "SSH-2.0-Erlang/4.10.5"
+ term = "", % e.g. "xterm"
width = 80,
height = 25,
pixel_width = 1024,
diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl
index 943e824de8..5bc2859b4e 100644
--- a/lib/ssh/src/ssh_auth.erl
+++ b/lib/ssh/src/ssh_auth.erl
@@ -232,10 +232,9 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
service = "ssh-connection",
method = "password",
data = <<?FALSE, ?UINT32(Sz), BinPwd:Sz/binary>>}, _,
- #ssh{opts = Opts,
- userauth_supported_methods = Methods} = Ssh) ->
+ #ssh{userauth_supported_methods = Methods} = Ssh) ->
Password = unicode:characters_to_list(BinPwd),
- case check_password(User, Password, Opts, Ssh) of
+ case check_password(User, Password, Ssh) of
{true,Ssh1} ->
{authorized, User,
{#ssh_msg_userauth_success{}, Ssh1}
@@ -287,10 +286,16 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
>>
},
_SessionId,
- #ssh{opts = Opts,
- userauth_supported_methods = Methods} = Ssh) ->
-
- case pre_verify_sig(User, KeyBlob, Opts) of
+ #ssh{userauth_supported_methods = Methods} = Ssh0) ->
+ Ssh =
+ case check_user(User, Ssh0) of
+ {true,Ssh01} -> Ssh01#ssh{user=User};
+ {false,Ssh01} -> Ssh01#ssh{user=false}
+ end,
+
+ case
+ pre_verify_sig(User, KeyBlob, Ssh)
+ of
true ->
{not_authorized, {User, undefined},
{#ssh_msg_userauth_pk_ok{algorithm_name = binary_to_list(BAlg),
@@ -312,10 +317,15 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
SigWLen/binary>>
},
SessionId,
- #ssh{userauth_supported_methods = Methods} = Ssh) ->
+ #ssh{user = PreVerifyUser,
+ userauth_supported_methods = Methods} = Ssh0) ->
- case verify_sig(SessionId, User, "ssh-connection",
- BAlg, KeyBlob, SigWLen, Ssh) of
+ {UserOk,Ssh} = check_user(User, Ssh0),
+ case
+ ((PreVerifyUser == User) orelse (PreVerifyUser == undefined)) andalso
+ UserOk andalso
+ verify_sig(SessionId, User, "ssh-connection", BAlg, KeyBlob, SigWLen, Ssh)
+ of
true ->
{authorized, User,
{#ssh_msg_userauth_success{}, Ssh}
@@ -429,7 +439,7 @@ handle_userauth_info_response(#ssh_msg_userauth_info_response{num_responses = 1,
orelse
proplists:get_value(one_empty, ?GET_OPT(tstflg,Opts), false),
- case check_password(User, unicode:characters_to_list(Password), Opts, Ssh) of
+ case check_password(User, unicode:characters_to_list(Password), Ssh) of
{true,Ssh1} when SendOneEmpty==true ->
{authorized_but_one_more, User,
{#ssh_msg_userauth_info_request{name = "",
@@ -473,8 +483,23 @@ method_preference(SigKeyAlgs) ->
],
PubKeyDefs ++ NonPKmethods.
-check_password(User, Password, Opts, Ssh) ->
+check_user(User, Ssh) ->
+ case ?GET_OPT(pk_check_user, Ssh#ssh.opts) of
+ true ->
+ check_password(User, pubkey, Ssh);
+ _ ->
+ {true, Ssh} % i.e, skip the test
+ end.
+
+check_password(User, Password, #ssh{opts=Opts} = Ssh) ->
case ?GET_OPT(pwdfun, Opts) of
+ undefined when Password==pubkey ->
+ %% Just check the User name
+ case lists:keysearch(User, 1, ?GET_OPT(user_passwords,Opts)) of
+ {value, {User, _}} -> {true, Ssh};
+ false -> {false, Ssh}
+ end;
+
undefined ->
Static = get_password_option(Opts, User),
{crypto:equal_const_time(Password,Static), Ssh};
@@ -508,7 +533,7 @@ get_password_option(Opts, User) ->
false -> ?GET_OPT(password, Opts)
end.
-pre_verify_sig(User, KeyBlob, Opts) ->
+pre_verify_sig(User, KeyBlob, #ssh{opts=Opts}) ->
try
Key = ssh_message:ssh2_pubkey_decode(KeyBlob), % or exception
ssh_transport:call_KeyCb(is_auth_key, [Key, User], Opts)
@@ -517,7 +542,7 @@ pre_verify_sig(User, KeyBlob, Opts) ->
false
end.
-verify_sig(SessionId, User, Service, AlgBin, KeyBlob, SigWLen, #ssh{opts = Opts} = Ssh) ->
+verify_sig(SessionId, User, Service, AlgBin, KeyBlob, SigWLen, #ssh{opts=Opts} = Ssh) ->
try
Alg = binary_to_list(AlgBin),
Key = ssh_message:ssh2_pubkey_decode(KeyBlob), % or exception
diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl
index fd2c2943f6..13a44beea3 100644
--- a/lib/ssh/src/ssh_cli.erl
+++ b/lib/ssh/src/ssh_cli.erl
@@ -41,6 +41,12 @@
cm,
channel,
pty,
+ encoding,
+ deduced_encoding, % OpenSSH sometimes lies about its encodeing. This variable
+ % is for the process of guessing the peer encoding, taylord
+ % after the behaviour of openssh. If it says latin1 it is so.
+ % It there arrives characters encoded in latin1 it is so. Otherwise
+ % assume utf8 until otherwise is proved.
group,
buf,
shell,
@@ -70,8 +76,9 @@ init([Shell]) ->
%%--------------------------------------------------------------------
handle_ssh_msg({ssh_cm, _ConnectionHandler,
{data, _ChannelId, _Type, Data}},
- #state{group = Group} = State) ->
- List = binary_to_list(Data),
+ #state{group = Group} = State0) ->
+ {Enc, State} = guess_encoding(Data, State0),
+ List = unicode:characters_to_list(Data, Enc),
to_group(List, Group),
{ok, State};
@@ -93,10 +100,69 @@ handle_ssh_msg({ssh_cm, ConnectionHandler,
{ok, State};
handle_ssh_msg({ssh_cm, ConnectionHandler,
- {env, ChannelId, WantReply, _Var, _Value}}, State) ->
+ {env, ChannelId, WantReply, Var, Value}}, State = #state{encoding=Enc0}) ->
+ %% It is not as simple as it sounds to set environment variables
+ %% in the server.
+ %% The (OS) env vars should be per per Channel; otherwise anyone
+ %% could affect anyone other's variables.
+ %% Therefore this clause always return a failure.
ssh_connection:reply_request(ConnectionHandler,
WantReply, failure, ChannelId),
- {ok, State};
+
+ %% https://pubs.opengroup.org/onlinepubs/7908799/xbd/envvar.html
+ %% LANG
+ %% This variable determines the locale category for native language,
+ %% local customs and coded character set in the absence of the LC_ALL
+ %% and other LC_* (LC_COLLATE, LC_CTYPE, LC_MESSAGES, LC_MONETARY,
+ %% LC_NUMERIC, LC_TIME) environment variables. This can be used by
+ %% applications to determine the language to use for error messages
+ %% and instructions, collating sequences, date formats, and so forth.
+ %% LC_ALL
+ %% This variable determines the values for all locale categories. The
+ %% value of the LC_ALL environment variable has precedence over any of
+ %% the other environment variables starting with LC_ (LC_COLLATE,
+ %% LC_CTYPE, LC_MESSAGES, LC_MONETARY, LC_NUMERIC, LC_TIME) and the
+ %% LANG environment variable.
+ %% ...
+ %%
+ %% The values of locale categories are determined by a precedence order;
+ %% the first condition met below determines the value:
+ %%
+ %% 1. If the LC_ALL environment variable is defined and is not null,
+ %% the value of LC_ALL is used.
+ %%
+ %% 2. If the LC_* environment variable ( LC_COLLATE, LC_CTYPE, LC_MESSAGES,
+ %% LC_MONETARY, LC_NUMERIC, LC_TIME) is defined and is not null, the
+ %% value of the environment variable is used to initialise the category
+ %% that corresponds to the environment variable.
+ %%
+ %% 3. If the LANG environment variable is defined and is not null, the value
+ %% of the LANG environment variable is used.
+ %%
+ %% 4. If the LANG environment variable is not set or is set to the empty string,
+ %% the implementation-dependent default locale is used.
+
+ Enc =
+ %% Rule 1 and 3 above says that LC_ALL has precedence over LANG. Since they
+ %% arrives in different messages and in an undefined order, it is resolved
+ %% like this:
+ case Var of
+ <<"LANG">> when Enc0==undefined ->
+ %% No previous LC_ALL
+ case claim_encoding(Value) of
+ {ok,Enc1} -> Enc1;
+ _ -> Enc0
+ end;
+ <<"LC_ALL">> ->
+ %% Maybe or maybe not a LANG has been handled, LC_ALL doesn't care
+ case claim_encoding(Value) of
+ {ok,Enc1} -> Enc1;
+ _ -> Enc0
+ end;
+ _ ->
+ Enc0
+ end,
+ {ok, State#state{encoding=Enc}};
handle_ssh_msg({ssh_cm, ConnectionHandler,
{window_change, ChannelId, Width, Height, PixWidth, PixHeight}},
@@ -114,15 +180,21 @@ handle_ssh_msg({ssh_cm, ConnectionHandler, {shell, ChannelId, WantReply}}, #sta
ssh_connection:exit_status(ConnectionHandler, ChannelId, ?EXEC_ERROR_STATUS),
ssh_connection:send_eof(ConnectionHandler, ChannelId),
{stop, ChannelId, State#state{channel = ChannelId, cm = ConnectionHandler}};
-handle_ssh_msg({ssh_cm, ConnectionHandler, {shell, ChannelId, WantReply}}, State) ->
+handle_ssh_msg({ssh_cm, ConnectionHandler, {shell, ChannelId, WantReply}}, State0) ->
+ State = case State0#state.encoding of
+ undefined -> State0#state{encoding = utf8};
+ _-> State0
+ end,
NewState = start_shell(ConnectionHandler, State),
ssh_connection:reply_request(ConnectionHandler, WantReply, success, ChannelId),
{ok, NewState#state{channel = ChannelId,
cm = ConnectionHandler}};
-handle_ssh_msg({ssh_cm, ConnectionHandler, {exec, ChannelId, WantReply, Cmd}}, S0) ->
+handle_ssh_msg({ssh_cm, ConnectionHandler, {exec, ChannelId, WantReply, Cmd0}}, S0) ->
+ {Enc,S1} = guess_encoding(Cmd0, S0),
+ Cmd = unicode:characters_to_list(Cmd0, Enc),
case
- case S0#state.exec of
+ case S1#state.exec of
disabled ->
{"Prohibited.", ?EXEC_ERROR_STATUS, 1};
@@ -130,7 +202,7 @@ handle_ssh_msg({ssh_cm, ConnectionHandler, {exec, ChannelId, WantReply, Cmd}},
%% Exec called and a Fun or MFA is defined to use. The F returns the
%% value to return.
%% The standard I/O is directed from/to the channel ChannelId.
- exec_direct(ConnectionHandler, ChannelId, Cmd, F, WantReply, S0);
+ exec_direct(ConnectionHandler, ChannelId, Cmd, F, WantReply, S1);
undefined when S0#state.shell == ?DEFAULT_SHELL ;
S0#state.shell == disabled ->
@@ -138,7 +210,7 @@ handle_ssh_msg({ssh_cm, ConnectionHandler, {exec, ChannelId, WantReply, Cmd}},
%% To be exact, eval the term as an Erlang term (but not using the
%% ?DEFAULT_SHELL directly). This disables banner, prompts and such.
%% The standard I/O is directed from/to the channel ChannelId.
- exec_in_erlang_default_shell(ConnectionHandler, ChannelId, Cmd, WantReply, S0);
+ exec_in_erlang_default_shell(ConnectionHandler, ChannelId, Cmd, WantReply, S1);
undefined ->
%% Exec called, but the a shell other than the default shell is defined.
@@ -150,17 +222,18 @@ handle_ssh_msg({ssh_cm, ConnectionHandler, {exec, ChannelId, WantReply, Cmd}},
%% Exec called and a Fun or MFA is defined to use. The F communicates via
%% standard io:write/read.
%% Kept for compatibility.
- S1 = start_exec_shell(ConnectionHandler, Cmd, S0),
+ S2 = start_exec_shell(ConnectionHandler, Cmd, S1),
ssh_connection:reply_request(ConnectionHandler, WantReply, success, ChannelId),
- {ok, S1}
+ {ok, S2}
end
of
{Reply, Status, Type} ->
- write_chars(ConnectionHandler, ChannelId, Type, Reply),
+ write_chars(ConnectionHandler, ChannelId, Type,
+ unicode:characters_to_binary(Reply, utf8, out_enc(S1))),
ssh_connection:reply_request(ConnectionHandler, WantReply, success, ChannelId),
ssh_connection:exit_status(ConnectionHandler, ChannelId, Status),
ssh_connection:send_eof(ConnectionHandler, ChannelId),
- {stop, ChannelId, S0#state{channel = ChannelId, cm = ConnectionHandler}};
+ {stop, ChannelId, S1#state{channel = ChannelId, cm = ConnectionHandler}};
{ok, S} ->
{ok, S#state{channel = ChannelId,
@@ -225,16 +298,26 @@ handle_msg({Group, tty_geometry}, #state{group = Group,
{ok,State};
handle_msg({Group, Req}, #state{group = Group, buf = Buf, pty = Pty,
- cm = ConnectionHandler,
- channel = ChannelId} = State) ->
- {Chars, NewBuf} = io_request(Req, Buf, Pty, Group),
+ cm = ConnectionHandler,
+ channel = ChannelId} = State) ->
+ {Chars0, NewBuf} = io_request(Req, Buf, Pty, Group),
+ Chars = unicode:characters_to_binary(Chars0, utf8, out_enc(State)),
write_chars(ConnectionHandler, ChannelId, Chars),
{ok, State#state{buf = NewBuf}};
-handle_msg({'EXIT', Group, _Reason}, #state{group = Group,
+handle_msg({'EXIT', Group, Reason}, #state{group = Group,
cm = ConnectionHandler,
channel = ChannelId} = State) ->
ssh_connection:send_eof(ConnectionHandler, ChannelId),
+ ExitStatus = case Reason of
+ normal ->
+ 0;
+ {exit_status, V} when is_integer(V) ->
+ V;
+ _ ->
+ ?EXEC_ERROR_STATUS
+ end,
+ ssh_connection:exit_status(ConnectionHandler, ChannelId, ExitStatus),
{stop, ChannelId, State};
handle_msg(_, State) ->
@@ -251,6 +334,61 @@ terminate(_Reason, _State) ->
%%% Internal functions
%%--------------------------------------------------------------------
+claim_encoding(<<"/", _/binary>>) ->
+ %% If the locale value begins with a slash, it is interpreted
+ %% as the pathname of a file that was created in the output format
+ %% used by the localedef utility; see OUTPUT FILES under localedef.
+ %% Referencing such a pathname will result in that locale being used
+ %% for the indicated category.
+ undefined;
+
+claim_encoding(EnvValue) ->
+ %% If the locale value has the form:
+ %% language[_territory][.codeset]
+ %% it refers to an implementation-provided locale, where settings of
+ %% language, territory and codeset are implementation-dependent.
+ try string:tokens(binary_to_list(EnvValue), ".")
+ of
+ [_,"UTF-8"] -> {ok,utf8};
+ [_,"ISO-8859-1"] -> {ok,latin1}; % There are -1 ... -16 called latin1..latin16
+ _ -> undefined
+ catch
+ _:_ -> undefined
+ end.
+
+
+guess_encoding(Data0, #state{encoding = PeerEnc0,
+ deduced_encoding = TestEnc0} = State) ->
+ Enc =
+ case {PeerEnc0,TestEnc0} of
+ {latin1,_} -> latin1;
+ {_,latin1} -> latin1;
+ _ -> case unicode:characters_to_binary(Data0, utf8, utf8) of
+ Data0 -> utf8;
+ _ -> latin1
+ end
+ end,
+ case TestEnc0 of
+ Enc ->
+ {Enc, State};
+ latin1 ->
+ {Enc, State};
+ utf8 when Enc==latin1 ->
+ {Enc, State#state{deduced_encoding=latin1}};
+ undefined ->
+ {Enc, State#state{deduced_encoding=Enc}}
+ end.
+
+
+out_enc(#state{encoding = PeerEnc,
+ deduced_encoding = DeducedEnc}) ->
+ case DeducedEnc of
+ undefined -> PeerEnc;
+ _ -> DeducedEnc
+ end.
+
+%%--------------------------------------------------------------------
+
to_group([], _Group) ->
ok;
to_group([$\^C | Tail], Group) ->
@@ -342,23 +480,29 @@ get_tty_command(left, N, _TerminalType) ->
%% convert input characters to buffer and to writeout
%% Note that the buf is reversed but the buftail is not
%% (this is handy; the head is always next to the cursor)
-conv_buf([], AccBuf, AccBufTail, AccWrite, Col) ->
+conv_buf([], AccBuf, AccBufTail, AccWrite, Col, _Tty) ->
{AccBuf, AccBufTail, lists:reverse(AccWrite), Col};
-conv_buf([13, 10 | Rest], _AccBuf, AccBufTail, AccWrite, _Col) ->
- conv_buf(Rest, [], tl2(AccBufTail), [10, 13 | AccWrite], 0);
-conv_buf([13 | Rest], _AccBuf, AccBufTail, AccWrite, _Col) ->
- conv_buf(Rest, [], tl1(AccBufTail), [13 | AccWrite], 0);
-conv_buf([10 | Rest], _AccBuf, AccBufTail, AccWrite, _Col) ->
- conv_buf(Rest, [], tl1(AccBufTail), [10, 13 | AccWrite], 0);
-conv_buf([C | Rest], AccBuf, AccBufTail, AccWrite, Col) ->
- conv_buf(Rest, [C | AccBuf], tl1(AccBufTail), [C | AccWrite], Col + 1).
+conv_buf([13, 10 | Rest], _AccBuf, AccBufTail, AccWrite, _Col, Tty) ->
+ conv_buf(Rest, [], tl2(AccBufTail), [10, 13 | AccWrite], 0, Tty);
+conv_buf([13 | Rest], _AccBuf, AccBufTail, AccWrite, _Col, Tty) ->
+ conv_buf(Rest, [], tl1(AccBufTail), [13 | AccWrite], 0, Tty);
+conv_buf([10 | Rest], _AccBuf, AccBufTail, AccWrite0, _Col, Tty) ->
+ AccWrite =
+ case pty_opt(onlcr,Tty) of
+ 0 -> [10 | AccWrite0];
+ 1 -> [10,13 | AccWrite0];
+ undefined -> [10 | AccWrite0]
+ end,
+ conv_buf(Rest, [], tl1(AccBufTail), AccWrite, 0, Tty);
+conv_buf([C | Rest], AccBuf, AccBufTail, AccWrite, Col, Tty) ->
+ conv_buf(Rest, [C | AccBuf], tl1(AccBufTail), [C | AccWrite], Col + 1, Tty).
%%% put characters at current position (possibly overwriting
%%% characters after current position in buffer)
-put_chars(Chars, {Buf, BufTail, Col}, _Tty) ->
+put_chars(Chars, {Buf, BufTail, Col}, Tty) ->
{NewBuf, NewBufTail, WriteBuf, NewCol} =
- conv_buf(Chars, Buf, BufTail, [], Col),
+ conv_buf(Chars, Buf, BufTail, [], Col, Tty),
{WriteBuf, {NewBuf, NewBufTail, NewCol}}.
%%% insert character at current position
@@ -366,7 +510,7 @@ insert_chars([], {Buf, BufTail, Col}, _Tty) ->
{[], {Buf, BufTail, Col}};
insert_chars(Chars, {Buf, BufTail, Col}, Tty) ->
{NewBuf, _NewBufTail, WriteBuf, NewCol} =
- conv_buf(Chars, Buf, [], [], Col),
+ conv_buf(Chars, Buf, [], [], Col, Tty),
M = move_cursor(special_at_width(NewCol+length(BufTail), Tty), NewCol, Tty),
{[WriteBuf, BufTail | M], {NewBuf, BufTail, NewCol}}.
@@ -620,11 +764,10 @@ exec_in_self_group(ConnectionHandler, ChannelId, WantReply, State, Fun) ->
end
of
{ok,Str} ->
- write_chars(ConnectionHandler, ChannelId, t2str(Str)),
- ssh_connection:exit_status(ConnectionHandler, ChannelId, 0);
+ write_chars(ConnectionHandler, ChannelId, t2str(Str));
{error, Str} ->
write_chars(ConnectionHandler, ChannelId, 1, "**Error** "++t2str(Str)),
- ssh_connection:exit_status(ConnectionHandler, ChannelId, ?EXEC_ERROR_STATUS)
+ exit({exit_status, ?EXEC_ERROR_STATUS})
end
end)
end,
@@ -639,14 +782,11 @@ t2str(T) -> try io_lib:format("~s",[T])
%%--------------------------------------------------------------------
% Pty can be undefined if the client never sets any pty options before
% starting the shell.
-get_echo(undefined) ->
- true;
-get_echo(#ssh_pty{modes = Modes}) ->
- case proplists:get_value(echo, Modes, 1) of
- 0 ->
- false;
- _ ->
- true
+get_echo(Tty) ->
+ case pty_opt(echo,Tty) of
+ 0 -> false;
+ 1 -> true;
+ undefined -> true
end.
% Group is undefined if the pty options are sent between open and
@@ -662,19 +802,107 @@ not_zero(0, B) ->
not_zero(A, _) ->
A.
+%%%----------------------------------------------------------------
+pty_opt(Name, Tty) ->
+ try
+ proplists:get_value(Name, Tty#ssh_pty.modes, undefined)
+ catch
+ _:_ -> undefined
+ end.
+
%%%################################################################
%%%#
%%%# Tracing
%%%#
-ssh_dbg_trace_points() -> [terminate].
+ssh_dbg_trace_points() -> [terminate, cli, cli_details].
+ssh_dbg_flags(cli) -> [c];
ssh_dbg_flags(terminate) -> [c].
+ssh_dbg_on(cli) -> dbg:tp(?MODULE,handle_ssh_msg,2,x),
+ dbg:tp(?MODULE,write_chars,4,x);
+ssh_dbg_on(cli_details) -> dbg:tp(?MODULE,handle_msg,2,x);
ssh_dbg_on(terminate) -> dbg:tp(?MODULE, terminate, 2, x).
+
+ssh_dbg_off(cli) -> dbg:ctpg(?MODULE,handle_ssh_msg,2),
+ dbg:ctpg(?MODULE,write_chars,4);
+ssh_dbg_off(cli_details) -> dbg:ctpg(?MODULE,handle_msg,2);
ssh_dbg_off(terminate) -> dbg:ctpg(?MODULE, terminate, 2).
+
+ssh_dbg_format(cli, {call,{?MODULE,handle_ssh_msg,
+ [{ssh_cm, _ConnectionHandler, Request},
+ S = #state{channel=Ch}]}}) when is_tuple(Request) ->
+ [io_lib:format("CLI conn ~p chan ~p, req ~p",
+ [self(),Ch,element(1,Request)]),
+ case Request of
+ {window_change, ChannelId, Width, Height, PixWidth, PixHeight} ->
+ fmt_kv([{channel_id,ChannelId},
+ {width,Width}, {height,Height},
+ {pix_width,PixWidth}, {pixel_hight,PixHeight}]);
+
+ {env, ChannelId, WantReply, Var, Value} ->
+ fmt_kv([{channel_id,ChannelId}, {want_reply,WantReply}, {Var,Value}]);
+
+ {exec, ChannelId, WantReply, Cmd} ->
+ fmt_kv([{channel_id,ChannelId}, {want_reply,WantReply}, {command,Cmd}]);
+
+ {pty, ChannelId, WantReply,
+ {TermName, Width, Height, PixWidth, PixHeight, Modes}} ->
+ fmt_kv([{channel_id,ChannelId}, {want_reply,WantReply},
+ {term,TermName},
+ {width,Width}, {height,Height}, {pix_width,PixWidth}, {pixel_hight,PixHeight},
+ {pty_opts, Modes}]);
+
+ {data, ChannelId, Type, Data} ->
+ fmt_kv([{channel_id,ChannelId},
+ {type, type(Type)},
+ {data, us, ssh_dbg:shrink_bin(Data)},
+ {hex, h, Data}
+ ]);
+
+ {shell, ChannelId, WantReply} ->
+ fmt_kv([{channel_id,ChannelId},
+ {want_reply,WantReply},
+ {encoding, S#state.encoding},
+ {pty, S#state.pty}
+ ]);
+
+ _ ->
+ io_lib:format("~nunder construction:~nRequest = ~p",[Request])
+ end];
+ssh_dbg_format(cli, {call,{?MODULE,handle_ssh_msg,_}}) -> skip;
+ssh_dbg_format(cli, {return_from,{?MODULE,handle_ssh_msg,2},_Result}) -> skip;
+
+ssh_dbg_format(cli, {call,{?MODULE,write_chars, [C, Ch, Type, Chars]}}) ->
+ [io_lib:format("CLI conn ~p chan ~p reply", [C,Ch]),
+ fmt_kv([{channel_id,Ch},
+ {type, type(Type)},
+ {data, us, ssh_dbg:shrink_bin(Chars)},
+ {hex, h, Chars}
+ ])];
+ssh_dbg_format(cli, {return_from,{?MODULE,write_chars,4},_Result}) -> skip;
+
+
+ssh_dbg_format(cli_details, {call,{?MODULE,handle_msg,
+ [{Group,Arg}, #state{channel=Ch}]}}) ->
+ [io_lib:format("CLI detail conn ~p chan ~p group ~p",
+ ['?', Ch, Group]),
+ case Arg of
+ {put_chars_sync,Class,Cs,Reply} ->
+ fmt_kv([{op, put_chars_sync},
+ {class, Class},
+ {data, us, ssh_dbg:shrink_bin(Cs)},
+ {hex, h, Cs},
+ {reply, Reply}]);
+ _ ->
+ io_lib:format("~nunder construction:~nRequest = ~p",[Arg])
+ end];
+ssh_dbg_format(cli_details, {call,{?MODULE,handle_msg,_}}) -> skip;
+ssh_dbg_format(cli_details, {return_from,{?MODULE,handle_msg,2},_Result}) -> skip;
+
ssh_dbg_format(terminate, {call, {?MODULE,terminate, [Reason, State]}}) ->
["Cli Terminating:\n",
io_lib:format("Reason: ~p,~nState:~n~s", [Reason, wr_record(State)])
@@ -684,3 +912,15 @@ ssh_dbg_format(terminate, {return_from, {?MODULE,terminate,2}, _Ret}) ->
?wr_record(state).
+
+fmt_kv(KVs) -> lists:map(fun fmt_kv1/1, KVs).
+
+fmt_kv1({K,V}) -> io_lib:format("~n~p: ~p",[K,V]);
+fmt_kv1({K,s,V}) -> io_lib:format("~n~p: ~s",[K,V]);
+fmt_kv1({K,us,V}) -> io_lib:format("~n~p: ~ts",[K,V]);
+fmt_kv1({K,h,V}) -> io_lib:format("~n~p: ~s",[K, [$\n|ssh_dbg:hex_dump(V)]]).
+
+type(0) -> "0 (normal data)";
+type(1) -> "1 (extended data, i.e. errors)";
+type(T) -> T.
+
diff --git a/lib/ssh/src/ssh_connect.hrl b/lib/ssh/src/ssh_connect.hrl
index 00d1320a78..e00e78d6b7 100644
--- a/lib/ssh/src/ssh_connect.hrl
+++ b/lib/ssh/src/ssh_connect.hrl
@@ -206,6 +206,7 @@
-define(IXANY,39). %% Any char will restart after stop.
-define(IXOFF,40). %% Enable input flow control.
-define(IMAXBEL,41). %% Ring bell on input queue full.
+-define(IUTF8,42). %% Terminal input and output is assumed to be encoded in UTF-8.
-define(ISIG,50). %% Enable signals INTR, QUIT, [D]SUSP.
-define(ICANON,51). %% Canonicalize input lines.
-define(XCASE,52). %% Enable input and output of uppercase characters by
diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl
index 9b9d66aaa8..a966f7bbf1 100644
--- a/lib/ssh/src/ssh_connection.erl
+++ b/lib/ssh/src/ssh_connection.erl
@@ -42,7 +42,7 @@
%% Internal SSH application API
-export([channel_data/5,
- handle_msg/3,
+ handle_msg/4,
handle_stop/1,
open_channel/4,
@@ -63,6 +63,8 @@
request_failure_msg/0,
request_success_msg/1,
+ send_environment_vars/3,
+
encode_ip/1
]).
@@ -322,7 +324,7 @@ adjust_window(ConnectionHandler, Channel, Bytes) ->
ssh_connection_handler:adjust_window(ConnectionHandler, Channel, Bytes).
%%--------------------------------------------------------------------
--spec setenv(ConnectionRef, ChannelId, Var, Value, Timeout) -> result() when
+-spec setenv(ConnectionRef, ChannelId, Var, Value, Timeout) -> success when
ConnectionRef :: ssh:connection_ref(),
ChannelId :: ssh:channel_id(),
Var :: string(),
@@ -332,12 +334,18 @@ adjust_window(ConnectionHandler, Channel, Bytes) ->
%%
%% Description: Environment variables may be passed to the shell/command to be
%% started later.
-%%--------------------------------------------------------------------
setenv(ConnectionHandler, ChannelId, Var, Value, TimeOut) ->
- ssh_connection_handler:request(ConnectionHandler, ChannelId,
- "env", true, [?string(Var), ?string(Value)], TimeOut).
-
-
+ setenv(ConnectionHandler, ChannelId, true, Var, Value, TimeOut).
+
+setenv(ConnectionHandler, ChannelId, WantReply, Var, Value, TimeOut) ->
+ case ssh_connection_handler:request(ConnectionHandler, ChannelId,
+ "env", WantReply,
+ [?string(Var), ?string(Value)], TimeOut) of
+ ok when WantReply == false ->
+ success;
+ Reply ->
+ Reply
+ end.
%%--------------------------------------------------------------------
-spec close(ConnectionRef, ChannelId) -> ok when
ConnectionRef :: ssh:connection_ref(),
@@ -464,7 +472,7 @@ handle_msg(#ssh_msg_channel_open_confirmation{recipient_channel = ChannelId,
sender_channel = RemoteId,
initial_window_size = WindowSz,
maximum_packet_size = PacketSz},
- #connection{channel_cache = Cache} = Connection0, _) ->
+ #connection{channel_cache = Cache} = Connection0, _, _SSH) ->
#channel{remote_id = undefined} = Channel =
ssh_client_channel:cache_lookup(Cache, ChannelId),
@@ -482,22 +490,22 @@ handle_msg(#ssh_msg_channel_open_failure{recipient_channel = ChannelId,
reason = Reason,
description = Descr,
lang = Lang},
- #connection{channel_cache = Cache} = Connection0, _) ->
+ #connection{channel_cache = Cache} = Connection0, _, _SSH) ->
Channel = ssh_client_channel:cache_lookup(Cache, ChannelId),
ssh_client_channel:cache_delete(Cache, ChannelId),
reply_msg(Channel, Connection0, {open_error, Reason, Descr, Lang});
-handle_msg(#ssh_msg_channel_success{recipient_channel = ChannelId}, Connection, _) ->
+handle_msg(#ssh_msg_channel_success{recipient_channel = ChannelId}, Connection, _, _SSH) ->
reply_msg(ChannelId, Connection, success);
-handle_msg(#ssh_msg_channel_failure{recipient_channel = ChannelId}, Connection, _) ->
+handle_msg(#ssh_msg_channel_failure{recipient_channel = ChannelId}, Connection, _, _SSH) ->
reply_msg(ChannelId, Connection, failure);
-handle_msg(#ssh_msg_channel_eof{recipient_channel = ChannelId}, Connection, _) ->
+handle_msg(#ssh_msg_channel_eof{recipient_channel = ChannelId}, Connection, _, _SSH) ->
reply_msg(ChannelId, Connection, {eof, ChannelId});
handle_msg(#ssh_msg_channel_close{recipient_channel = ChannelId},
- #connection{channel_cache = Cache} = Connection0, _) ->
+ #connection{channel_cache = Cache} = Connection0, _, _SSH) ->
case ssh_client_channel:cache_lookup(Cache, ChannelId) of
#channel{sent_close = Closed, remote_id = RemoteId,
@@ -530,18 +538,18 @@ handle_msg(#ssh_msg_channel_close{recipient_channel = ChannelId},
handle_msg(#ssh_msg_channel_data{recipient_channel = ChannelId,
data = Data},
- Connection, _) ->
+ Connection, _, _SSH) ->
channel_data_reply_msg(ChannelId, Connection, 0, Data);
handle_msg(#ssh_msg_channel_extended_data{recipient_channel = ChannelId,
data_type_code = DataType,
data = Data},
- Connection, _) ->
+ Connection, _, _SSH) ->
channel_data_reply_msg(ChannelId, Connection, DataType, Data);
handle_msg(#ssh_msg_channel_window_adjust{recipient_channel = ChannelId,
bytes_to_add = Add},
- #connection{channel_cache = Cache} = Connection, _) ->
+ #connection{channel_cache = Cache} = Connection, _, _SSH) ->
#channel{send_window_size = Size, remote_id = RemoteId} =
Channel0 = ssh_client_channel:cache_lookup(Cache, ChannelId),
@@ -560,7 +568,7 @@ handle_msg(#ssh_msg_channel_open{channel_type = "session" = Type,
initial_window_size = WindowSz,
maximum_packet_size = PacketSz},
#connection{options = SSHopts} = Connection0,
- server) ->
+ server, _SSH) ->
MinAcceptedPackSz =
?GET_OPT(minimal_remote_max_packet_size, SSHopts),
@@ -598,7 +606,7 @@ handle_msg(#ssh_msg_channel_open{channel_type = "forwarded-tcpip",
options = Options,
sub_system_supervisor = SubSysSup
} = C,
- client) ->
+ client, _SSH) ->
{ReplyMsg, NextChId} =
case ssh_connection_handler:retrieve(C, {tcpip_forward,ConnectedHost,ConnectedPort}) of
{ok, {ConnectToHost,ConnectToPort}} ->
@@ -656,7 +664,7 @@ handle_msg(#ssh_msg_channel_open{channel_type = "direct-tcpip",
options = Options,
sub_system_supervisor = SubSysSup
} = C,
- server) ->
+ server, _SSH) ->
{ReplyMsg, NextChId} =
case ?GET_OPT(tcpip_tunnel_in, Options) of
%% May add more to the option, like allowed ip/port pairs to connect to
@@ -706,7 +714,7 @@ handle_msg(#ssh_msg_channel_open{channel_type = "direct-tcpip",
handle_msg(#ssh_msg_channel_open{channel_type = "session",
sender_channel = RemoteId},
Connection,
- client) ->
+ client, _SSH) ->
%% Client implementations SHOULD reject any session channel open
%% requests to make it more difficult for a corrupt server to attack the
%% client. See See RFC 4254 6.1.
@@ -715,7 +723,7 @@ handle_msg(#ssh_msg_channel_open{channel_type = "session",
"Connection refused", "en"),
{[{connection_reply, FailMsg}], Connection};
-handle_msg(#ssh_msg_channel_open{sender_channel = RemoteId}, Connection, _) ->
+handle_msg(#ssh_msg_channel_open{sender_channel = RemoteId}, Connection, _, _SSH) ->
FailMsg = channel_open_failure_msg(RemoteId,
?SSH_OPEN_ADMINISTRATIVELY_PROHIBITED,
"Not allowed", "en"),
@@ -724,7 +732,7 @@ handle_msg(#ssh_msg_channel_open{sender_channel = RemoteId}, Connection, _) ->
handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
request_type = "exit-status",
data = Data},
- Connection, _) ->
+ Connection, _, _SSH) ->
<<?UINT32(Status)>> = Data,
reply_msg(ChannelId, Connection, {exit_status, ChannelId, Status});
@@ -732,7 +740,7 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
request_type = "exit-signal",
want_reply = false,
data = Data},
- #connection{channel_cache = Cache} = Connection0, _) ->
+ #connection{channel_cache = Cache} = Connection0, _, _SSH) ->
<<?DEC_BIN(SigName, _SigLen),
?BOOLEAN(_Core),
?DEC_BIN(Err, _ErrLen),
@@ -751,7 +759,7 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
request_type = "xon-xoff",
want_reply = false,
data = Data},
- Connection, _) ->
+ Connection, _, _SSH) ->
<<?BOOLEAN(CDo)>> = Data,
reply_msg(ChannelId, Connection, {xon_xoff, ChannelId, CDo=/= 0});
@@ -759,7 +767,7 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
request_type = "window-change",
want_reply = false,
data = Data},
- Connection0, _) ->
+ Connection0, _, _SSH) ->
<<?UINT32(Width),?UINT32(Height),
?UINT32(PixWidth), ?UINT32(PixHeight)>> = Data,
reply_msg(ChannelId, Connection0, {window_change, ChannelId,
@@ -769,7 +777,7 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
request_type = "signal",
data = Data},
- Connection0, _) ->
+ Connection0, _, _SSH) ->
<<?DEC_BIN(SigName, _SigLen)>> = Data,
reply_msg(ChannelId, Connection0, {signal, ChannelId,
binary_to_list(SigName)});
@@ -778,7 +786,7 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
request_type = "subsystem",
want_reply = WantReply,
data = Data},
- #connection{channel_cache = Cache} = Connection, server) ->
+ #connection{channel_cache = Cache} = Connection, server, _SSH) ->
<<?DEC_BIN(SsName,_SsLen)>> = Data,
#channel{remote_id=RemoteId} = Channel =
ssh_client_channel:cache_lookup(Cache, ChannelId),
@@ -795,7 +803,7 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
{[{connection_reply,Reply}], Connection};
handle_msg(#ssh_msg_channel_request{request_type = "subsystem"},
- Connection, client) ->
+ Connection, client, _SSH) ->
%% The client SHOULD ignore subsystem requests. See RFC 4254 6.5.
{[], Connection};
@@ -803,31 +811,48 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
request_type = "pty-req",
want_reply = WantReply,
data = Data},
- Connection, server) ->
+ Connection, server, SSH) ->
<<?DEC_BIN(BTermName,_TermLen),
?UINT32(Width),?UINT32(Height),
?UINT32(PixWidth), ?UINT32(PixHeight),
Modes/binary>> = Data,
TermName = binary_to_list(BTermName),
+ PtyOpts0 = decode_pty_opts(Modes),
+ PtyOpts = case SSH#ssh.c_version of
+ "SSH-2.0-PuTTY"++_ ->
+ %% If - peer client is PuTTY
+ %% - it asked for pty
+ %% - did not tell if LF->CRLF expansion is wanted
+ %% then
+ %% - do LF->CRLF expansion
+ case proplists:get_value(onlcr, PtyOpts0, undefined) of
+ undefined ->
+ [{onlcr,1} | PtyOpts0];
+ _ ->
+ PtyOpts0
+ end;
+ _ ->
+ PtyOpts0
+ end,
PtyRequest = {TermName, Width, Height,
- PixWidth, PixHeight, decode_pty_opts(Modes)},
+ PixWidth, PixHeight, PtyOpts},
handle_cli_msg(Connection, ChannelId,
{pty, ChannelId, WantReply, PtyRequest});
handle_msg(#ssh_msg_channel_request{request_type = "pty-req"},
- Connection, client) ->
+ Connection, client, _SSH) ->
%% The client SHOULD ignore pty requests. See RFC 4254 6.2.
{[], Connection};
handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
request_type = "shell",
want_reply = WantReply},
- Connection, server) ->
+ Connection, server, _SSH) ->
handle_cli_msg(Connection, ChannelId,
{shell, ChannelId, WantReply});
handle_msg(#ssh_msg_channel_request{request_type = "shell"},
- Connection, client) ->
+ Connection, client, _SSH) ->
%% The client SHOULD ignore shell requests. See RFC 4254 6.5.
{[], Connection};
@@ -835,13 +860,13 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
request_type = "exec",
want_reply = WantReply,
data = Data},
- Connection, server) ->
+ Connection, server, _SSH) ->
<<?DEC_BIN(Command, _Len)>> = Data,
handle_cli_msg(Connection, ChannelId,
{exec, ChannelId, WantReply, binary_to_list(Command)});
handle_msg(#ssh_msg_channel_request{request_type = "exec"},
- Connection, client) ->
+ Connection, client, _SSH) ->
%% The client SHOULD ignore exec requests. See RFC 4254 6.5.
{[], Connection};
@@ -849,36 +874,44 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
request_type = "env",
want_reply = WantReply,
data = Data},
- Connection, server) ->
+ Connection, server, _SSH) ->
<<?DEC_BIN(Var,_VarLen), ?DEC_BIN(Value,_ValLen)>> = Data,
handle_cli_msg(Connection, ChannelId,
{env, ChannelId, WantReply, Var, Value});
handle_msg(#ssh_msg_channel_request{request_type = "env"},
- Connection, client) ->
+ Connection, client, _SSH) ->
%% The client SHOULD ignore env requests.
{[], Connection};
handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
- request_type = _Other,
- want_reply = WantReply},
- #connection{channel_cache = Cache} = Connection, _) ->
- if WantReply == true ->
- case ssh_client_channel:cache_lookup(Cache, ChannelId) of
- #channel{remote_id = RemoteId} ->
- FailMsg = channel_failure_msg(RemoteId),
- {[{connection_reply, FailMsg}], Connection};
- undefined -> %% Chanel has been closed
- {[], Connection}
- end;
- true ->
- {[], Connection}
+ want_reply = WantReply},
+ #connection{channel_cache = Cache} = Connection, _, _SSH) ->
+ %% Not a valid request_type. All valid types are handling the
+ %% parameter checking in their own clauses above.
+ %%
+ %% The special ReqType faulty_msg signals that something went
+ %% wrong found during decoding.
+ %%
+ %% RFC4254 5.4 says:
+ %% "If 'want reply' is FALSE, no response will be sent to the request.
+ %% Otherwise, the recipient responds with either
+ %% SSH_MSG_CHANNEL_SUCCESS, SSH_MSG_CHANNEL_FAILURE, or request-specific
+ %% continuation messages. If the request is not recognized or is not
+ %% supported for the channel, SSH_MSG_CHANNEL_FAILURE is returned."
+ %%
+ case ssh_client_channel:cache_lookup(Cache, ChannelId) of
+ #channel{remote_id = RemoteId} when WantReply==true ->
+ FailMsg = channel_failure_msg(RemoteId),
+ {[{connection_reply, FailMsg}], Connection};
+ _ -> %% Channel has been closed or no reply is wanted
+ {[], Connection}
end;
handle_msg(#ssh_msg_global_request{name = <<"tcpip-forward">>,
want_reply = WantReply,
data = <<?DEC_BIN(ListenAddrStr,_Len),?UINT32(ListenPort)>>},
- #connection{options = Opts} = Connection, server) ->
+ #connection{options = Opts} = Connection, server, _SSH) ->
case ?GET_OPT(tcpip_tunnel_out, Opts) of
false ->
%% This daemon instance has not enabled tcpip_forwarding
@@ -913,7 +946,7 @@ handle_msg(#ssh_msg_global_request{name = <<"tcpip-forward">>,
handle_msg(#ssh_msg_global_request{name = _Type,
want_reply = WantReply,
- data = _Data}, Connection, _Role) ->
+ data = _Data}, Connection, _Role, _SSH) ->
if WantReply == true ->
FailMsg = request_failure_msg(),
{[{connection_reply, FailMsg}], Connection};
@@ -922,29 +955,29 @@ handle_msg(#ssh_msg_global_request{name = _Type,
end;
handle_msg(#ssh_msg_request_failure{},
- #connection{requests = [{_, From} | Rest]} = Connection, _) ->
+ #connection{requests = [{_, From} | Rest]} = Connection, _, _SSH) ->
{[{channel_request_reply, From, {failure, <<>>}}],
Connection#connection{requests = Rest}};
handle_msg(#ssh_msg_request_failure{},
- #connection{requests = [{_, From,_} | Rest]} = Connection, _) ->
+ #connection{requests = [{_, From,_} | Rest]} = Connection, _, _SSH) ->
{[{channel_request_reply, From, {failure, <<>>}}],
Connection#connection{requests = Rest}};
handle_msg(#ssh_msg_request_success{data = Data},
- #connection{requests = [{_, From} | Rest]} = Connection, _) ->
+ #connection{requests = [{_, From} | Rest]} = Connection, _, _SSH) ->
{[{channel_request_reply, From, {success, Data}}],
Connection#connection{requests = Rest}};
handle_msg(#ssh_msg_request_success{data = Data},
- #connection{requests = [{_, From, Fun} | Rest]} = Connection0, _) ->
+ #connection{requests = [{_, From, Fun} | Rest]} = Connection0, _, _SSH) ->
Connection = Fun({success,Data}, Connection0),
{[{channel_request_reply, From, {success, Data}}],
Connection#connection{requests = Rest}};
handle_msg(#ssh_msg_disconnect{code = Code,
description = Description},
- Connection, _) ->
+ Connection, _, _SSH) ->
{disconnect, {Code, Description}, handle_stop(Connection)}.
@@ -1315,6 +1348,8 @@ encode_pty_opts2([{ixoff,Value} | Opts]) ->
[?IXOFF, ?uint32(Value) | encode_pty_opts2(Opts)];
encode_pty_opts2([{imaxbel,Value} | Opts]) ->
[?IMAXBEL, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{iutf8,Value} | Opts]) ->
+ [?IUTF8, ?uint32(Value) | encode_pty_opts2(Opts)];
encode_pty_opts2([{isig,Value} | Opts]) ->
[?ISIG, ?uint32(Value) | encode_pty_opts2(Opts)];
encode_pty_opts2([{icanon,Value} | Opts]) ->
@@ -1409,6 +1444,7 @@ decode_pty_opts2(<<Code, ?UINT32(Value), Tail/binary>>) ->
?IXANY -> ixany;
?IXOFF -> ixoff;
?IMAXBEL -> imaxbel;
+ ?IUTF8 -> iutf8; % RFC 8160
?ISIG -> isig;
?ICANON -> icanon;
?XCASE -> xcase;
@@ -1530,3 +1566,15 @@ request_reply_or_data(#channel{local_id = ChannelId, user = ChannelPid},
{[{channel_data, ChannelPid, Reply}], Connection}
end.
+%%%----------------------------------------------------------------
+send_environment_vars(ConnectionHandler, Channel, VarNames) ->
+ lists:foldl(
+ fun(Var, success) ->
+ case os:getenv(Var) of
+ false ->
+ success;
+ Value ->
+ setenv(ConnectionHandler, Channel, false,
+ Var, Value, infinity)
+ end
+ end, success, VarNames).
diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl
index 2745f2c76a..a198a95937 100644
--- a/lib/ssh/src/ssh_connection_handler.erl
+++ b/lib/ssh/src/ssh_connection_handler.erl
@@ -1109,7 +1109,7 @@ handle_event(_, {#ssh_msg_kexinit{},_}, {connected,Role}, D0) ->
handle_event(_, #ssh_msg_disconnect{description=Desc} = Msg, StateName, D0) ->
{disconnect, _, RepliesCon} =
- ssh_connection:handle_msg(Msg, D0#data.connection_state, role(StateName)),
+ ssh_connection:handle_msg(Msg, D0#data.connection_state, role(StateName), D0#data.ssh_params),
{Actions,D} = send_replies(RepliesCon, D0),
disconnect_fun("Received disconnect: "++Desc, D),
{stop_and_reply, {shutdown,Desc}, Actions, D};
@@ -1129,7 +1129,7 @@ handle_event(internal, {conn_msg,Msg}, StateName, #data{starter = User,
event_queue = Qev0} = D0) ->
Role = role(StateName),
Rengotation = renegotiation(StateName),
- try ssh_connection:handle_msg(Msg, Connection0, Role) of
+ try ssh_connection:handle_msg(Msg, Connection0, Role, D0#data.ssh_params) of
{disconnect, Reason0, RepliesConn} ->
{Repls, D} = send_replies(RepliesConn, D0),
case {Reason0,Role} of
@@ -1239,12 +1239,20 @@ handle_event(cast, {adjust_window,ChannelId,Bytes}, StateName, D) when ?CONNECTE
keep_state_and_data
end;
-handle_event(cast, {reply_request,success,ChannelId}, StateName, D) when ?CONNECTED(StateName) ->
+handle_event(cast, {reply_request,Resp,ChannelId}, StateName, D) when ?CONNECTED(StateName) ->
case ssh_client_channel:cache_lookup(cache(D), ChannelId) of
- #channel{remote_id = RemoteId} ->
- Msg = ssh_connection:channel_success_msg(RemoteId),
- update_inet_buffers(D#data.socket),
- {keep_state, send_msg(Msg,D)};
+ #channel{remote_id = RemoteId} when Resp== success ; Resp==failure ->
+ Msg =
+ case Resp of
+ success -> ssh_connection:channel_success_msg(RemoteId);
+ failure -> ssh_connection:channel_failure_msg(RemoteId)
+ end,
+ update_inet_buffers(D#data.socket),
+ {keep_state, send_msg(Msg,D)};
+
+ #channel{} ->
+ Details = io_lib:format("Unhandled reply in state ~p:~n~p", [StateName,Resp]),
+ ?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR, Details, StateName, D);
undefined ->
keep_state_and_data
diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl
index f8750dc070..54a88a479f 100644
--- a/lib/ssh/src/ssh_dbg.erl
+++ b/lib/ssh/src/ssh_dbg.erl
@@ -417,7 +417,7 @@ try_all_types_in_all_modules(TypesOn, Arg, WriteFun, Acc0) ->
write_txt(WriteFun, TS, PID, Txt) when is_list(Txt) ->
- WriteFun("~n~s ~p ~s~n",
+ WriteFun("~n~s ~p ~ts~n",
[lists:flatten(TS),
PID,
lists:flatten(Txt)],
diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl
index e5ab449b8b..fab9c50867 100644
--- a/lib/ssh/src/ssh_message.erl
+++ b/lib/ssh/src/ssh_message.erl
@@ -366,13 +366,25 @@ decode(<<?BYTE(?SSH_MSG_CHANNEL_CLOSE), ?UINT32(Recipient)>>) ->
recipient_channel = Recipient
};
decode(<<?BYTE(?SSH_MSG_CHANNEL_REQUEST), ?UINT32(Recipient),
- ?DEC_BIN(RequestType,__0), ?BYTE(Bool), Data/binary>>) ->
- #ssh_msg_channel_request{
- recipient_channel = Recipient,
- request_type = ?unicode_list(RequestType),
- want_reply = erl_boolean(Bool),
- data = Data
- };
+ ?DEC_BIN(RequestType,__0), ?BYTE(Bool), Data/binary>>=Bytes) ->
+ try
+ #ssh_msg_channel_request{
+ recipient_channel = Recipient,
+ request_type = ?unicode_list(RequestType),
+ want_reply = erl_boolean(Bool),
+ data = Data
+ }
+ catch _:_ ->
+ %% Faulty, RFC4254 says:
+ %% "If the request is not recognized or is not
+ %% supported for the channel, SSH_MSG_CHANNEL_FAILURE is returned."
+ %% So we provoke such a message to be sent
+ #ssh_msg_channel_request{
+ recipient_channel = Recipient,
+ request_type = faulty_msg,
+ data = Bytes
+ }
+ end;
decode(<<?BYTE(?SSH_MSG_CHANNEL_SUCCESS), ?UINT32(Recipient)>>) ->
#ssh_msg_channel_success{
recipient_channel = Recipient
diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl
index 2135afece4..b5b0a9d2d8 100644
--- a/lib/ssh/src/ssh_options.erl
+++ b/lib/ssh/src/ssh_options.erl
@@ -455,6 +455,12 @@ default(server) ->
class => user_option
},
+ pk_check_user =>
+ #{default => false,
+ chk => fun(V) -> erlang:is_boolean(V) end,
+ class => user_option
+ },
+
password =>
#{default => undefined,
chk => fun(V) -> check_string(V) end,
diff --git a/lib/ssh/src/ssh_shell.erl b/lib/ssh/src/ssh_shell.erl
index be8a6aa8cc..bd11afa080 100644
--- a/lib/ssh/src/ssh_shell.erl
+++ b/lib/ssh/src/ssh_shell.erl
@@ -92,7 +92,7 @@ handle_ssh_msg({ssh_cm, _, {data, _ChannelId, 0, Data}}, State) ->
%% TODO: When unicode support is ready
%% should we call this function or perhaps a new
%% function.
- io:put_chars(Data),
+ io:format("~ts", [Data]),
{ok, State};
handle_ssh_msg({ssh_cm, _,
@@ -101,7 +101,7 @@ handle_ssh_msg({ssh_cm, _,
%% TODO: When unicode support is ready
%% should we call this function or perhaps a new
%% function.
- io:put_chars(Data),
+ io:format("~ts", [Data]),
{ok, State};
handle_ssh_msg({ssh_cm, _, {eof, _ChannelId}}, State) ->
@@ -144,9 +144,18 @@ handle_msg({input, IoPid, eof}, #state{io = IoPid, channel = ChannelId,
ssh_connection:send_eof(ConnectionManager, ChannelId),
{ok, State};
-handle_msg({input, IoPid, Line}, #state{io = IoPid,
+handle_msg({input, IoPid, Line0}, #state{io = IoPid,
channel = ChannelId,
cm = ConnectionManager} = State) ->
+ %% Not nice, but it make it work somehow
+ Line = case encoding(Line0) of
+ utf8 ->
+ Line0;
+ unicode ->
+ unicode:characters_to_binary(Line0);
+ latin1 ->
+ unicode:characters_to_binary(Line0,latin1,utf8)
+ end,
ssh_connection:send(ConnectionManager, ChannelId, Line),
{ok, State}.
@@ -161,9 +170,19 @@ terminate(_Reason, #state{io = IoPid}) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
+encoding(Bin) ->
+ case unicode:characters_to_binary(Bin,utf8,utf8) of
+ Bin ->
+ utf8;
+ Bin2 when is_binary(Bin2) ->
+ unicode;
+ _ ->
+ latin1
+ end.
+%%--------------------------------------------------------------------
input_loop(Fd, Pid) ->
- case io:get_line(Fd, '>') of
+ case io:get_line(Fd, '') of
eof ->
Pid ! {input, self(), eof},
ok;
@@ -189,14 +208,58 @@ get_ancestors() ->
%%%# Tracing
%%%#
-ssh_dbg_trace_points() -> [terminate].
+ssh_dbg_trace_points() -> [terminate, shell].
+ssh_dbg_flags(shell) -> [c];
ssh_dbg_flags(terminate) -> [c].
+ssh_dbg_on(shell) -> dbg:tp(?MODULE,handle_ssh_msg,2,x);
ssh_dbg_on(terminate) -> dbg:tp(?MODULE, terminate, 2, x).
+ssh_dbg_off(shell) -> dbg:ctpg(?MODULE,handle_ssh_msg,2);
ssh_dbg_off(terminate) -> dbg:ctpg(?MODULE, terminate, 2).
+
+ssh_dbg_format(shell, {call,{?MODULE,handle_ssh_msg,
+ [{ssh_cm, _ConnectionHandler, Request},
+ #state{channel=Ch}]}}) when is_tuple(Request) ->
+ [io_lib:format("SHELL conn ~p chan ~p, req ~p",
+ [self(),Ch,element(1,Request)]),
+ case Request of
+ {window_change, ChannelId, Width, Height, PixWidth, PixHeight} ->
+ fmt_kv([{channel_id,ChannelId},
+ {width,Width}, {height,Height},
+ {pix_width,PixWidth}, {pixel_hight,PixHeight}]);
+
+ {env, ChannelId, WantReply, Var, Value} ->
+ fmt_kv([{channel_id,ChannelId}, {want_reply,WantReply}, {Var,Value}]);
+
+ {exec, ChannelId, WantReply, Cmd} ->
+ fmt_kv([{channel_id,ChannelId}, {want_reply,WantReply}, {command,Cmd}]);
+
+ {pty, ChannelId, WantReply,
+ {TermName, Width, Height, PixWidth, PixHeight, Modes}} ->
+ fmt_kv([{channel_id,ChannelId}, {want_reply,WantReply},
+ {term,TermName},
+ {width,Width}, {height,Height}, {pix_width,PixWidth}, {pixel_hight,PixHeight},
+ {pty_opts, Modes}]);
+
+ {data, ChannelId, Type, Data} ->
+ fmt_kv([{channel_id,ChannelId},
+ {type, case Type of
+ 0 -> "0 (normal data)";
+ 1 -> "1 (extended data, i.e. errors)";
+ _ -> Type
+ end},
+ {data, ssh_dbg:shrink_bin(Data)},
+ {hex, h, Data}
+ ]);
+ _ ->
+ io_lib:format("~nunder construction:~nRequest = ~p",[Request])
+ end];
+ssh_dbg_format(shell, {call,{?MODULE,handle_ssh_msg,_}}) -> skip;
+ssh_dbg_format(shell, {return_from,{?MODULE,handle_ssh_msg,2},_Result}) -> skip;
+
ssh_dbg_format(terminate, {call, {?MODULE,terminate, [Reason, State]}}) ->
["Shell Terminating:\n",
io_lib:format("Reason: ~p,~nState:~n~s", [Reason, wr_record(State)])
@@ -205,3 +268,10 @@ ssh_dbg_format(terminate, {return_from, {?MODULE,terminate,2}, _Ret}) ->
skip.
?wr_record(state).
+
+fmt_kv(KVs) -> lists:map(fun fmt_kv1/1, KVs).
+
+fmt_kv1({K,V}) -> io_lib:format("~n~p: ~p",[K,V]);
+fmt_kv1({K,s,V}) -> io_lib:format("~n~p: ~s",[K,V]);
+fmt_kv1({K,h,V}) -> io_lib:format("~n~p: ~s",[K, [$\n|ssh_dbg:hex_dump(V)]]).
+
diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl
index db3a4f7699..5c6798bbcb 100644
--- a/lib/ssh/test/ssh_basic_SUITE.erl
+++ b/lib/ssh/test/ssh_basic_SUITE.erl
@@ -46,6 +46,8 @@
basic_test/1,
check_error/1,
cli/1,
+ cli_exit_normal/1,
+ cli_exit_status/1,
close/1,
daemon_already_started/1,
daemon_error_closes_port/1,
@@ -151,7 +153,7 @@ groups() ->
{p_basic, [?PARALLEL], [send, peername_sockname,
exec, exec_compressed,
exec_with_io_out, exec_with_io_in,
- cli,
+ cli, cli_exit_normal, cli_exit_status,
idle_time_client, idle_time_server, openssh_zlib_basic_test,
misc_ssh_options, inet_option, inet6_option,
shell, shell_socket, shell_ssh_conn, shell_no_unicode, shell_unicode_string,
@@ -607,6 +609,94 @@ cli(Config) when is_list(Config) ->
30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
end.
+%%-----------------------------------------------------------------------------
+%%% Test that SSH client receives exit-status 0 on successful command execution
+cli_exit_normal(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
+ UserDir = proplists:get_value(priv_dir, Config),
+
+ {_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir},
+ {password, "morot"},
+ {ssh_cli, {ssh_cli, [fun (_) -> spawn(fun () -> ok end) end]}},
+ {subsystems, []},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+ ct:sleep(500),
+
+ 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),
+ ssh_connection:shell(ConnectionRef, ChannelId),
+
+ receive
+ {ssh_cm, ConnectionRef,{eof, ChannelId}} ->
+ ok
+ after
+ 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
+ end,
+
+ receive
+ {ssh_cm, ConnectionRef,{exit_status,ChannelId,0}} ->
+ ok
+ after
+ 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
+ end,
+
+ receive
+ {ssh_cm, ConnectionRef,{closed, ChannelId}} ->
+ ok
+ after
+ 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
+ end.
+
+%%---------------------------------------------------------
+%%% Test that SSH client receives user provided exit-status
+cli_exit_status(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
+ UserDir = proplists:get_value(priv_dir, Config),
+
+ {_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir},
+ {password, "morot"},
+ {ssh_cli, {ssh_cli, [fun (_) -> spawn(fun () -> exit({exit_status, 7}) end) end]}},
+ {subsystems, []},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+ ct:sleep(500),
+
+ 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),
+ ssh_connection:shell(ConnectionRef, ChannelId),
+
+ receive
+ {ssh_cm, ConnectionRef,{eof, ChannelId}} ->
+ ok
+ after
+ 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
+ end,
+
+ receive
+ {ssh_cm, ConnectionRef,{exit_status,ChannelId,7}} ->
+ ok
+ after
+ 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
+ end,
+
+ receive
+ {ssh_cm, ConnectionRef,{closed, ChannelId}} ->
+ ok
+ after
+ 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
+ end.
+
%%--------------------------------------------------------------------
%%% Test that get correct error message if you try to start a daemon
%%% on an adress that already runs a daemon see also seq10667
@@ -1421,66 +1511,13 @@ basic_test(Config) ->
ok = ssh:close(CM),
ssh:stop_daemon(Pid).
-do_shell(IO, Shell) ->
- receive
- ErlPrompt0 ->
- ct:log("Erlang prompt: ~p~n", [ErlPrompt0])
- end,
- IO ! {input, self(), "1+1.\r\n"},
- receive
- Echo0 ->
- ct:log("Echo: ~p ~n", [Echo0])
- after
- 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
- end,
- receive
- ?NEWLINE ->
- ok
- after
- 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
- end,
- receive
- Result0 = <<"2">> ->
- ct:log("Result: ~p~n", [Result0])
- after
- 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
- end,
- receive
- ?NEWLINE ->
- ok
- after
- 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
- end,
- receive
- ErlPrompt1 ->
- ct:log("Erlang prompt: ~p~n", [ErlPrompt1])
- after
- 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
- end,
- exit(Shell, kill).
- %%Does not seem to work in the testserver!
- %% IO ! {input, self(), "q().\r\n"},
- %% receive
- %% ?NEWLINE ->
- %% ok
- %% end,
- %% receive
- %% Echo1 ->
- %% ct:log("Echo: ~p ~n", [Echo1])
- %% end,
- %% receive
- %% ?NEWLINE ->
- %% ok
- %% end,
- %% receive
- %% Result1 ->
- %% ct:log("Result: ~p~n", [Result1])
- %% end,
- %% receive
- %% {'EXIT', Shell, killed} ->
- %% ok
- %% end.
-
+do_shell(IO, _Shell) ->
+ new_do_shell(IO, [new_prompt,
+ {type,"1+1."},
+ {expect,"2"},
+ new_prompt,
+ {type,"exit()."}
+ ]).
%%--------------------------------------------------------------------
wait_for_erlang_first_line(Config) ->
@@ -1576,7 +1613,7 @@ new_do_shell_prompt(IO, N, type, Str, More) ->
ct:log("Matched prompt ~p to trigger sending of next line to server",[N]),
IO ! {input, self(), Str++"\r\n"},
ct:log("Promt '~p> ', Sent ~ts",[N,Str++"\r\n"]),
- new_do_shell(IO, N, [{expect_echo,Str}|More]); % expect echo of the sent line
+ new_do_shell(IO, N, More);
new_do_shell_prompt(IO, N, Op, Str, More) ->
ct:log("Matched prompt ~p",[N]),
new_do_shell(IO, N, [{Op,Str}|More]).
diff --git a/lib/ssh/test/ssh_connection_SUITE.erl b/lib/ssh/test/ssh_connection_SUITE.erl
index e4cbe8045f..84f47803c4 100644
--- a/lib/ssh/test/ssh_connection_SUITE.erl
+++ b/lib/ssh/test/ssh_connection_SUITE.erl
@@ -73,6 +73,7 @@
start_exec_direct_fun1_read_write/1,
start_exec_direct_fun1_read_write_advanced/1,
start_shell/1,
+ start_shell_pty/1,
start_shell_exec/1,
start_shell_exec_direct_fun/1,
start_shell_exec_direct_fun1_error/1,
@@ -112,6 +113,7 @@ all() ->
exec_disabled,
exec_shell_disabled,
start_shell,
+ start_shell_pty,
start_shell_exec,
start_shell_exec_fun,
start_shell_exec_fun2,
@@ -578,7 +580,28 @@ start_shell(Config) when is_list(Config) ->
{password, "morot"},
{user_interaction, true},
{user_dir, UserDir}]),
- test_shell_is_enabled(ConnectionRef, <<"Enter command\r\n">>),
+ test_shell_is_enabled(ConnectionRef, <<"Enter command">>), % No pty alloc by erl client
+ test_exec_is_disabled(ConnectionRef),
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid).
+
+%%-------------------------------------------------------------------
+start_shell_pty(Config) when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
+ file:make_dir(UserDir),
+ SysDir = proplists:get_value(data_dir, Config),
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
+ {user_dir, UserDir},
+ {password, "morot"},
+ {shell, fun(U, H) -> start_our_shell(U, H) end} ]),
+
+ ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "foo"},
+ {password, "morot"},
+ {user_interaction, true},
+ {user_dir, UserDir}]),
+ test_shell_is_enabled(ConnectionRef, <<"Enter command\r\n">>, [{pty_opts,[{onlcr,1}]}]), % alloc pty
test_exec_is_disabled(ConnectionRef),
ssh:close(ConnectionRef),
ssh:stop_daemon(Pid).
@@ -692,42 +715,54 @@ exec_shell_disabled(Config) when is_list(Config) ->
%%--------------------------------------------------------------------
start_shell_exec_fun(Config) ->
- do_start_shell_exec_fun(fun ssh_exec_echo/1,
- "testing", <<"echo testing\r\n">>, 0,
+ do_start_shell_exec_fun(fun(Cmd) ->
+ spawn(fun() ->
+ io:format("echo ~s\n", [Cmd])
+ end)
+ end,
+ "testing", <<"echo testing\n">>, 0,
Config).
start_shell_exec_fun2(Config) ->
- do_start_shell_exec_fun(fun ssh_exec_echo/2,
- "testing", <<"echo foo testing\r\n">>, 0,
+ do_start_shell_exec_fun(fun(Cmd, User) ->
+ spawn(fun() ->
+ io:format("echo ~s ~s\n",[User,Cmd])
+ end)
+ end,
+ "testing", <<"echo foo testing\n">>, 0,
Config).
start_shell_exec_fun3(Config) ->
- do_start_shell_exec_fun(fun ssh_exec_echo/3,
- "testing", <<"echo foo testing\r\n">>, 0,
+ do_start_shell_exec_fun(fun(Cmd, User, _PeerAddr) ->
+ spawn(fun() ->
+ io:format("echo ~s ~s\n",[User,Cmd])
+ end)
+ end,
+ "testing", <<"echo foo testing\n">>, 0,
Config).
start_shell_exec_direct_fun(Config) ->
- do_start_shell_exec_fun({direct, fun ssh_exec_direct_echo/1},
+ do_start_shell_exec_fun({direct, fun(Cmd) -> {ok, io_lib:format("echo ~s~n",[Cmd])} end},
"testing", <<"echo testing\n">>, 0,
Config).
start_shell_exec_direct_fun2(Config) ->
- do_start_shell_exec_fun({direct, fun ssh_exec_direct_echo/2},
+ do_start_shell_exec_fun({direct, fun(Cmd,User) -> {ok, io_lib:format("echo ~s ~s",[User,Cmd])} end},
"testing", <<"echo foo testing">>, 0,
Config).
start_shell_exec_direct_fun3(Config) ->
- do_start_shell_exec_fun({direct, fun ssh_exec_direct_echo/3},
+ do_start_shell_exec_fun({direct, fun(Cmd,User,_PeerAddr) -> {ok, io_lib:format("echo ~s ~s",[User,Cmd])} end},
"testing", <<"echo foo testing">>, 0,
Config).
start_shell_exec_direct_fun1_error(Config) ->
- do_start_shell_exec_fun({direct, fun ssh_exec_direct_echo_error_return/1},
+ do_start_shell_exec_fun({direct, fun(_Cmd) -> {error, {bad}} end},
"testing", <<"**Error** {bad}">>, 1,
Config).
start_shell_exec_direct_fun1_error_type(Config) ->
- do_start_shell_exec_fun({direct, fun ssh_exec_direct_echo_error_return_type/1},
+ do_start_shell_exec_fun({direct, fun(_Cmd) -> very_bad end},
"testing", <<"**Error** Bad exec fun in server. Invalid return value: very_bad">>, 1,
Config).
@@ -750,11 +785,11 @@ start_exec_direct_fun1_read_write(Config) ->
{ok, Ch} = ssh_connection:session_channel(C, infinity),
success = ssh_connection:exec(C, Ch, "> ", infinity),
- ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"Tiny read/write test\r\n">>}}),
+ ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"Tiny read/write test\n">>}}),
ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"1> ">>}}),
ok = ssh_connection:send(C, Ch, "hej.\n", 5000),
- ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"{simple_eval,hej}\r\n">>}}),
+ ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"{simple_eval,hej}\n">>}}),
ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"2> ">>}}),
ok = ssh_connection:send(C, Ch, "quit.\n", 5000),
@@ -798,18 +833,18 @@ start_exec_direct_fun1_read_write_advanced(Config) ->
{ok, Ch} = ssh_connection:session_channel(C, infinity),
success = ssh_connection:exec(C, Ch, "> ", infinity),
- ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"Tiny read/write test\r\n">>}}),
+ ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"Tiny read/write test\n">>}}),
ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"1> ">>}}),
ok = ssh_connection:send(C, Ch, "hej.\n", 5000),
- ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"{simple_eval,hej}\r\n">>}}),
+ ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"{simple_eval,hej}\n">>}}),
ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"2> ">>}}),
ok = ssh_connection:send(C, Ch, "'Hi ", 5000),
ok = ssh_connection:send(C, Ch, "there", 5000),
ok = ssh_connection:send(C, Ch, "'", 5000),
ok = ssh_connection:send(C, Ch, ".\n", 5000),
- ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"{simple_eval,'Hi there'}\r\n">>}}),
+ ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"{simple_eval,'Hi there'}\n">>}}),
ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"3> ">>}}),
ok = ssh_connection:send(C, Ch, "bad_input.\n", 5000),
ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,1,<<"**Error** {bad_input,3}">>}}),
@@ -880,7 +915,8 @@ do_start_shell_exec_fun(Fun, Command, Expect, ExpectType, Config) ->
after 5000 ->
receive
Other ->
- ct:log("Received other:~n~p",[Other]),
+ ct:log("Received other:~n~p~nExpected: ~p~n",
+ [Other, {ssh_cm, ConnectionRef, {data, '_ChannelId', ExpectType, Expect}} ]),
ct:fail("Unexpected response")
after 0 ->
ct:fail("Exec Timeout")
@@ -915,7 +951,7 @@ start_shell_sock_exec_fun(Config) when is_list(Config) ->
"testing", infinity),
receive
- {ssh_cm, ConnectionRef, {data, _ChannelId, 0, <<"echo testing\r\n">>}} ->
+ {ssh_cm, ConnectionRef, {data, _ChannelId, 0, <<"echo testing\n">>}} ->
ok
after 5000 ->
ct:fail("Exec Timeout")
@@ -959,7 +995,7 @@ start_shell_sock_daemon_exec(Config) ->
"testing", infinity),
receive
- {ssh_cm, ConnectionRef, {data, _ChannelId, 0, <<"echo testing\r\n">>}} ->
+ {ssh_cm, ConnectionRef, {data, _ChannelId, 0, <<"echo testing\n">>}} ->
ok
after 5000 ->
ct:fail("Exec Timeout")
@@ -1018,7 +1054,7 @@ start_shell_sock_daemon_exec_multi(Config) ->
success = ssh_connection:exec(ConnectionRef, ChannelId0, "testing", infinity),
ct:log("~p:~p: exec on connection ~p", [?MODULE,?LINE,ConnectionRef]),
receive
- {ssh_cm, ConnectionRef, {data, _ChannelId, 0, <<"echo testing\r\n">>}} ->
+ {ssh_cm, ConnectionRef, {data, _ChannelId, 0, <<"echo testing\n">>}} ->
Parent ! {answer_received,self()},
ct:log("~p:~p: recevied result on connection ~p", [?MODULE,?LINE,ConnectionRef])
after 5000 ->
@@ -1157,7 +1193,7 @@ stop_listener(Config) when is_list(Config) ->
success = ssh_connection:exec(ConnectionRef0, ChannelId0,
"testing", infinity),
receive
- {ssh_cm, ConnectionRef0, {data, ChannelId0, 0, <<"echo testing\r\n">>}} ->
+ {ssh_cm, ConnectionRef0, {data, ChannelId0, 0, <<"echo testing\n">>}} ->
ok
after 5000 ->
ct:fail("Exec Timeout")
@@ -1368,7 +1404,8 @@ test_shell_is_disabled(ConnectionRef, Expect, NotExpect) ->
ct:fail("Could start disabled shell!");
R ->
- ct:log("~p:~p Got unexpected ~p",[?MODULE,?LINE,R]),
+ ct:log("~p:~p Got unexpected ~p~nExpect: ~p~n",
+ [?MODULE,?LINE,R, {ssh_cm, ConnectionRef, {data, ChannelId, '0|1', Expect}} ]),
ct:fail("Strange shell response")
after 5000 ->
@@ -1383,7 +1420,8 @@ test_exec_is_disabled(ConnectionRef) ->
{ssh_cm, ConnectionRef, {data,ChannelId,1,<<"Prohibited.">>}} ->
flush_msgs();
R ->
- ct:log("~p:~p Got unexpected ~p",[?MODULE,?LINE,R]),
+ ct:log("~p:~p Got unexpected ~p~nExpect: ~p~n",
+ [?MODULE,?LINE,R, {ssh_cm, ConnectionRef, {data,ChannelId,1,<<"Prohibited.">>}} ]),
ct:fail("Could exec erlang term although non-erlang shell")
after 5000 ->
ct:fail("Exec Timeout")
@@ -1394,15 +1432,25 @@ test_shell_is_enabled(ConnectionRef) ->
test_shell_is_enabled(ConnectionRef, <<"Eshell V">>).
test_shell_is_enabled(ConnectionRef, Expect) ->
+ test_shell_is_enabled(ConnectionRef, Expect, []).
+
+test_shell_is_enabled(ConnectionRef, Expect, PtyOpts) ->
{ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
ok = ssh_connection:shell(ConnectionRef,ChannelId),
+ case PtyOpts of
+ [] ->
+ no_alloc;
+ _ ->
+ success = ssh_connection:ptty_alloc(ConnectionRef, ChannelId, PtyOpts)
+ end,
ExpSz = size(Expect),
receive
{ssh_cm,ConnectionRef, {data, ChannelId, 0, <<Expect:ExpSz/binary, _/binary>>}} ->
flush_msgs();
R ->
- ct:log("~p:~p Got unexpected ~p",[?MODULE,?LINE,R]),
+ ct:log("~p:~p Got unexpected ~p~nExpect: ~p~n",
+ [?MODULE,?LINE,R, {ssh_cm, ConnectionRef, {data, ChannelId, 0, Expect}} ]),
ct:fail("Strange shell response")
after 5000 ->
@@ -1421,7 +1469,8 @@ test_exec_is_enabled(ConnectionRef, Exec, Expect) ->
{ssh_cm, ConnectionRef, {data, ChannelId, 0, <<Expect:ExpSz/binary, _/binary>>}} = R ->
ct:log("~p:~p Got expected ~p",[?MODULE,?LINE,R]);
Other ->
- ct:log("~p:~p Got unexpected ~p",[?MODULE,?LINE,Other])
+ ct:log("~p:~p Got unexpected ~p~nExpect: ~p~n",
+ [?MODULE,?LINE, Other, {ssh_cm, ConnectionRef, {data, ChannelId, 0, Expect}} ])
after 5000 ->
{fail,"Exec Timeout"}
end.
@@ -1502,12 +1551,3 @@ ssh_exec_echo(Cmd, User) ->
spawn(fun() ->
io:format("echo ~s ~s\n",[User,Cmd])
end).
-ssh_exec_echo(Cmd, User, _PeerAddr) ->
- ssh_exec_echo(Cmd,User).
-
-ssh_exec_direct_echo(Cmd) -> {ok, io_lib:format("echo ~s~n",[Cmd])}.
-ssh_exec_direct_echo(Cmd, User) -> {ok, io_lib:format("echo ~s ~s",[User,Cmd])}.
-ssh_exec_direct_echo(Cmd, User, _PeerAddr) -> ssh_exec_direct_echo(Cmd,User).
-
-ssh_exec_direct_echo_error_return(_Cmd) -> {error, {bad}}.
-ssh_exec_direct_echo_error_return_type(_Cmd) -> very_bad.
diff --git a/lib/ssh/test/ssh_options_SUITE.erl b/lib/ssh/test/ssh_options_SUITE.erl
index f13edeefe2..1dbfb0249b 100644
--- a/lib/ssh/test/ssh_options_SUITE.erl
+++ b/lib/ssh/test/ssh_options_SUITE.erl
@@ -1332,19 +1332,6 @@ one_shell_op(IO, TimeOut) ->
IO ! {input, self(), "2*3*7.\r\n"},
receive
- Echo0 -> ct:log("Echo: ~p ~n", [Echo0])
- after TimeOut -> ct:fail("Timeout waiting for echo")
- end,
-
- receive
- ?NEWLINE -> ct:log("NEWLINE received", [])
- after TimeOut ->
- receive Any1 -> ct:log("Bad NEWLINE: ~p",[Any1])
- after 0 -> ct:fail("Timeout waiting for NEWLINE")
- end
- end,
-
- receive
Result0 -> ct:log("Result: ~p~n", [Result0])
after TimeOut -> ct:fail("Timeout waiting for result")
end.
@@ -1409,7 +1396,7 @@ max_sessions(Config, ParallelLogin, Connect0) when is_function(Connect0,2) ->
ct:log("Connections up: ~p",[Connections]),
[_|_] = Connections,
- %% Now try one more than alowed:
+ %% N w try one more than alowed:
ct:pal("Info Report expected here (if not disabled) ...",[]),
try Connect(Host,Port)
of
diff --git a/lib/ssh/test/ssh_sup_SUITE.erl b/lib/ssh/test/ssh_sup_SUITE.erl
index 957dbafda6..8ad405b1d7 100644
--- a/lib/ssh/test/ssh_sup_SUITE.erl
+++ b/lib/ssh/test/ssh_sup_SUITE.erl
@@ -305,6 +305,7 @@ shell_channel_tree(Config) ->
{ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
ok = ssh_connection:shell(ConnectionRef,ChannelId0),
+ success = ssh_connection:ptty_alloc(ConnectionRef, ChannelId0, [{pty_opts,[{onlcr,1}]}]),
?wait_match([{_, GroupPid,worker,[ssh_server_channel]}],
supervisor:which_children(ChannelSup),
diff --git a/lib/ssh/test/ssh_test_lib.erl b/lib/ssh/test/ssh_test_lib.erl
index 0516d6ef3f..a9591547dd 100644
--- a/lib/ssh/test/ssh_test_lib.erl
+++ b/lib/ssh/test/ssh_test_lib.erl
@@ -338,6 +338,7 @@ loop_io_server(TestCase, Buff0) ->
%%ct:log("io_server ~p:~p ~p got ~p",[?MODULE,?LINE,self(),_REQ]),
{ok, Reply, Buff} = io_request(Request, TestCase, From,
ReplyAs, Buff0),
+ %%ct:log("io_server ~p:~p ~p going to reply ~p",[?MODULE,?LINE,self(),Reply]),
io_reply(From, ReplyAs, Reply),
loop_io_server(TestCase, Buff);
{'EXIT',_, _} = _Exit ->
@@ -347,6 +348,12 @@ loop_io_server(TestCase, Buff0) ->
30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
end.
+io_request(getopts,_TestCase, _, _, Buff) ->
+ {ok, [], Buff};
+io_request({get_geometry,columns},_TestCase, _, _, Buff) ->
+ {ok, 80, Buff};
+io_request({get_geometry,rows},_TestCase, _, _, Buff) ->
+ {ok, 24, Buff};
io_request({put_chars, Chars}, TestCase, _, _, Buff) ->
reply(TestCase, Chars),
{ok, ok, Buff};
@@ -354,7 +361,7 @@ io_request({put_chars, unicode, Chars}, TestCase, _, _, Buff) when is_binary(Cha
reply(TestCase, Chars),
{ok, ok, Buff};
io_request({put_chars, unicode, io_lib, format, [Fmt,Args]}, TestCase, _, _, Buff) ->
- reply(TestCase, io_lib:format(Fmt,Args)),
+ reply(TestCase, unicode:characters_to_binary(io_lib:format(Fmt,Args))),
{ok, ok, Buff};
io_request({put_chars, Enc, Chars}, TestCase, _, _, Buff) ->
reply(TestCase, unicode:characters_to_binary(Chars,Enc,latin1)),
@@ -370,6 +377,7 @@ io_request({get_line, _Enc, _Prompt} = Request, _, From, ReplyAs, [] = Buff) ->
io_request({get_line, _Enc,_}, _, _, _, [Line | Buff]) ->
{ok, Line, Buff}.
+
io_reply(_, _, []) ->
ok;
io_reply(From, ReplyAs, Reply) ->
diff --git a/lib/ssh/vsn.mk b/lib/ssh/vsn.mk
index a766d27020..8dadccc559 100644
--- a/lib/ssh/vsn.mk
+++ b/lib/ssh/vsn.mk
@@ -1,4 +1,4 @@
#-*-makefile-*- ; force emacs to enter makefile-mode
-SSH_VSN = 4.10.5
+SSH_VSN = 4.10.7
APP_VSN = "ssh-$(SSH_VSN)"
diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml
index b95f098931..ee5b40dea8 100644
--- a/lib/ssl/doc/src/notes.xml
+++ b/lib/ssl/doc/src/notes.xml
@@ -27,6 +27,216 @@
</header>
<p>This document describes the changes made to the SSL application.</p>
+<section><title>SSL 10.2.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Avoid race when the first two upgrade server handshakes
+ (that is servers that use a gen_tcp socket as input to
+ ssl:handshake/2,3) start close to each other. Could lead
+ to that one of the handshakes would fail.</p>
+ <p>
+ Own Id: OTP-17190 Aux Id: ERIERL-606 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SSL 10.2.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Avoid that upgrade (from TCP to TLS) servers starts
+ multiple session cache handlers for the same server. This
+ applies to Erlang distribution over TLS servers.</p>
+ <p>
+ Own Id: OTP-17139 Aux Id: ERL-1458, OTP-16239 </p>
+ </item>
+ <item>
+ <p>
+ Legacy cipher suites defined before TLS-1.2 (but still
+ supported) should be possible to use in TLS-1.2. They
+ where accidentally excluded for available cipher suites
+ for TLS-1.2 in OTP-23.2.2.</p>
+ <p>
+ Own Id: OTP-17174 Aux Id: ERIERL-597 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Enable Erlang distribution over TLS to run TLS-1.3,
+ although TLS-1.2 will still be default.</p>
+ <p>
+ Own Id: OTP-16239 Aux Id: ERL-1458, OTP-17139 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SSL 10.2.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix CVE-2020-35733 this only affects ssl-10.2 (OTP-23.2).
+ This vulnerability could enable a man in the middle
+ attack using a fake chain to a known trusted ROOT. Also
+ limits alternative chain handling, for handling of
+ possibly extraneous certs, to improve memory management.</p>
+ <p>
+ Own Id: OTP-17098</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Add support for AES CCM based cipher suites defined in
+ RFC 7251</p>
+ <p>
+ Also Correct cipher suite name conversion to OpenSSL
+ names. A few names where corrected earlier in OTP-16267
+ For backwards compatible reasons we support usage of
+ openSSL names for cipher suites. Mostly anonymous suites
+ names where incorrect, but also some legacy suites.</p>
+ <p>
+ Own Id: OTP-17100</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SSL 10.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ SSL's Erlang Distribution Protocol modules inet_tls_dist
+ and inet6_tls_dist lacked a callback function, so the
+ start flag "-dist_listen false" did not work, which has
+ now been fixed.</p>
+ <p>
+ Own Id: OTP-15126 Aux Id: ERL-1375 </p>
+ </item>
+ <item>
+ <p>
+ Correct OpenSSL names for newer cipher suites using DHE
+ in their name that accidentally got the wrong value when
+ fixing other older names using EDH instead.</p>
+ <p>
+ Own Id: OTP-16267 Aux Id: ERIERL-571, ERIERL-477 </p>
+ </item>
+ <item>
+ <p>
+ This change improves the handling of DTLS listening
+ dockets, making it possible to open multiple listeners on
+ the same port with different IP addresses.</p>
+ <p>
+ Own Id: OTP-16849 Aux Id: ERL-1339 </p>
+ </item>
+ <item>
+ <p>
+ Fix a bug that causes cross-build failure.</p>
+ <p>
+ This change excludes the ssl.d dependency file from the
+ source tarballs.</p>
+ <p>
+ Own Id: OTP-16921</p>
+ </item>
+ <item>
+ <p>
+ This change fixes ssl:peername/1 when called on a DTLS
+ client socket.</p>
+ <p>
+ Own Id: OTP-16923 Aux Id: ERL-1341, PR-2786 </p>
+ </item>
+ <item>
+ <p>
+ Retain emulation of active once on a closed socket to
+ behave as before 23.1</p>
+ <p>
+ Own Id: OTP-17018 Aux Id: ERL-1409 </p>
+ </item>
+ <item>
+ <p>
+ Corrected server session cache entry deletion pre
+ TLS-1.3. May increase session reuse.</p>
+ <p>
+ Own Id: OTP-17019 Aux Id: ERL-1412 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Handle extraneous certs in certificate chains as well as
+ chains that are incomplete but can be reconstructed or
+ unordered chains. The cert and certfile options will now
+ accept a list of certificates so that the user may
+ specify the chain explicitly.</p>
+ <p>
+ Also, the default value of the depth option has been
+ increased to allow longer chains by default.</p>
+ <p>
+ Own Id: OTP-16277</p>
+ </item>
+ <item>
+ <p>
+ This change implements optional NSS-style keylog in
+ ssl:connection_information/2 for debugging purposes.</p>
+ <p>
+ The keylog contains various TLS secrets that can be
+ loaded in Wireshark to decrypt TLS packets.</p>
+ <p>
+ Own Id: OTP-16445 Aux Id: PR-2823 </p>
+ </item>
+ <item>
+ <p>
+ Use new gen_statem feature of changing callback mode to
+ improve code maintainability.</p>
+ <p>
+ Own Id: OTP-16529</p>
+ </item>
+ <item>
+ <p>
+ The handling of Service Name Indication has been aligned
+ with RFC8446.</p>
+ <p>
+ Own Id: OTP-16762</p>
+ </item>
+ <item>
+ <p>
+ Add explicit session reuse option to TLS clients for pre
+ TLS-1.3 sessions. Also, add documentation to Users Guide
+ for such sessions.</p>
+ <p>
+ Own Id: OTP-16893</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>SSL 10.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -296,6 +506,33 @@
</section>
+<section><title>SSL 9.6.2.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Correct flow ctrl checks from OTP-16764 to work as
+ intended. Probably will not have a noticeable affect but
+ will make connections more well behaved under some
+ circumstances.</p>
+ <p>
+ Own Id: OTP-16837 Aux Id: ERL-1319, OTP-16764 </p>
+ </item>
+ <item>
+ <p>
+ Fix a bug that causes cross-build failure.</p>
+ <p>
+ This change excludes the ssl.d dependency file from the
+ source tar balls.</p>
+ <p>
+ Own Id: OTP-16921</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>SSL 9.6.2.2</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml
index 596fbf45f3..5bd27e1d9a 100644
--- a/lib/ssl/doc/src/ssl.xml
+++ b/lib/ssl/doc/src/ssl.xml
@@ -304,15 +304,29 @@
<datatype>
<name name="cert"/>
<desc>
- <p>The DER-encoded users certificate. If this option
- is supplied, it overrides option <c>certfile</c>.</p>
+ <p>The DER-encoded users certificate. Note that the cert option may also
+ be a list of DER-encoded certificates where the first one is the users
+ certificate and the rest of the certificates constitutes the
+ certificate chain. For maximum interoperability the
+ certificates in the chain should be in the correct order, the
+ chain will be sent as is to the peer. If chain certificates
+ are not provided, certificates from <seetype
+ marker="#client_cacerts">client_cacerts()</seetype>, <seetype
+ marker="#server_cacerts">server_cacerts()</seetype>, or
+ <seetype marker="#client_cafile">client_cafile()</seetype>,
+ <seetype marker="#server_cafile">server_cafile()</seetype> are
+ used to construct the chain. If this option is supplied, it
+ overrides option <c>certfile</c>.</p>
</desc>
</datatype>
<datatype>
<name name="cert_pem"/>
<desc>
- <p>Path to a file containing the user certificate on PEM format.</p>
+ <p>Path to a file containing the user certificate on PEM format or possible several
+ certificates where the first one is the users certificate and the rest of the certificates
+ constitutes the certificate chain. For more details see <seetype marker="#cert">cert()</seetype>,
+ </p>
</desc>
</datatype>
@@ -437,7 +451,7 @@
in a valid certification path. So, if depth is 0 the PEER must
be signed by the trusted ROOT-CA directly; if 1 the path can
be PEER, CA, ROOT-CA; if 2 the path can be PEER, CA, CA,
- ROOT-CA, and so on. The default value is 1.</p>
+ ROOT-CA, and so on. The default value is 10.</p>
</desc>
</datatype>
@@ -788,6 +802,16 @@ fun(srp, Username :: binary(), UserState :: term()) ->
</desc>
</datatype>
+ <datatype>
+ <name name="keep_secrets"/>
+ <desc><p>Configures a TLS 1.3 connection for keylogging</p>
+ <p>In order to retrieve keylog information on a TLS 1.3 connection, it must be configured
+ in advance to keep the client_random and various handshake secrets.</p>
+ <p>The keep_secrets functionality is disabled (<c>false</c>) by default.</p>
+ <p>Added in OTP 23.2</p>
+ </desc>
+ </datatype>
+
<datatype_title>TLS/DTLS OPTION DESCRIPTIONS - CLIENT</datatype_title>
<datatype>
@@ -804,8 +828,12 @@ fun(srp, Username :: binary(), UserState :: term()) ->
<datatype>
<name name="client_reuse_session"/>
<desc>
- <p>Reuses a specific session earlier saved with the option
- <c>{reuse_sessions, save} since OTP-21.3 </c>
+ <p>Reuses a specific session. The session should be refered by its session id if it is
+ earlier saved with the option <c>{reuse_sessions, save}</c> since OTP-21.3 or
+ explicitly specified by its session id and associated data since OTP-22.3.
+ See also
+ <seeguide marker="ssl:using_ssl#session-reuse-pre-tls-1.3">
+ SSL's Users Guide, Session Reuse pre TLS 1.3</seeguide>
</p>
</desc>
</datatype>
@@ -1445,7 +1473,7 @@ fun(srp, Username :: binary(), UserState :: term()) ->
Indication extension will be sent, and <seemfa
marker="public_key:public_key#pkix_verify_hostname/2">public_key:pkix_verify_hostname/2</seemfa>
will be called with the IP-address of the connection as
- <c>ReferenceID</c>, which is proably not what you want.</p>
+ <c>ReferenceID</c>, which is probably not what you want.</p>
</note>
<p> If the option <c>{handshake, hello}</c> is used the
@@ -1550,7 +1578,7 @@ fun(srp, Username :: binary(), UserState :: term()) ->
</fsummary>
<desc><p>Returns the requested information items about the connection,
if they are defined.</p>
- <p>Note that client_random, server_random and master_secret are values
+ <p>Note that client_random, server_random, master_secret and keylog are values
that affect the security of connection. Meaningful atoms, not specified
above, are the ssl option names.</p>
diff --git a/lib/ssl/doc/src/ssl_protocol.xml b/lib/ssl/doc/src/ssl_protocol.xml
index a1f561323d..aae08a2c9e 100644
--- a/lib/ssl/doc/src/ssl_protocol.xml
+++ b/lib/ssl/doc/src/ssl_protocol.xml
@@ -137,28 +137,42 @@
</section>
<section>
- <title>TLS Sessions</title>
+ <title>TLS Sessions - PRE TLS-1.3</title>
- <p>From the TLS RFC: "A TLS session is an association between a
- client and a server. Sessions are created by the handshake
- protocol. Sessions define a set of cryptographic security
- parameters, which can be shared among multiple
- connections. Sessions are used to avoid the expensive negotiation
- of new security parameters for each connection."</p>
+ <p>From the TLS RFC: "A TLS session is an association between a
+ client and a server. Sessions are created by the handshake
+ protocol. Sessions define a set of cryptographic security
+ parameters, which can be shared among multiple
+ connections. Sessions are used to avoid the expensive negotiation
+ of new security parameters for each connection."</p>
- <p>Session data is by default kept by the SSL application in a
- memory storage, hence session data is lost at application
- restart or takeover. Users can define their own callback module
- to handle session data storage if persistent data storage is
- required. Session data is also invalidated after 24 hours
- from it was saved, for security reasons. The amount of time the
- session data is to be saved can be configured.</p>
+ <p>Session data is by default kept by the SSL application in a
+ memory storage, hence session data is lost at application
+ restart or takeover. Users can define their own callback module
+ to handle session data storage if persistent data storage is
+ required. Session data is also invalidated when session
+ database exceeds its limit or 24 hours after being saved
+ (RFC max lifetime recommendation). The amount of time the
+ session data is to be saved can be configured.</p>
- <p>By default the TLS/DTLS clients try to reuse an available session and
- by default the TLS/DTLS servers agree to reuse sessions when clients
- ask for it.</p>
+ <p>By default the TLS/DTLS clients try to reuse an available
+ session and by default the TLS/DTLS servers agree to reuse
+ sessions when clients ask for it. See also
+ <seeguide marker="ssl:using_ssl#session-reuse-pre-tls-1.3"> Session Reuse Pre TLS-1.3 </seeguide>
+ </p>
+ </section>
+ <section>
+ <title>TLS-1.3 session tickets</title>
+
+ <p>In TLS 1.3 the session reuse is replaced by a new session
+ tickets mechanism based on the pre shared key concept. This
+ mechanism also obsoletes the session tickets from RFC5077, not
+ implemented by this application. See also
+ <seeguide marker="ssl:using_ssl#session-tickets-and-session-resumption-in-tls-1.3">Session Tickets and Session Resumption in TLS-1.3 </seeguide>
+ </p>
</section>
+
</chapter>
diff --git a/lib/ssl/doc/src/standards_compliance.xml b/lib/ssl/doc/src/standards_compliance.xml
index 5e97c5c20b..6cf25d726f 100644
--- a/lib/ssl/doc/src/standards_compliance.xml
+++ b/lib/ssl/doc/src/standards_compliance.xml
@@ -297,8 +297,8 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">server_name (RFC6066)</cell>
- <cell align="left" valign="middle"><em>PC</em></cell>
- <cell align="left" valign="middle"><em>22.2</em></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.2</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -436,8 +436,8 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">server_name (RFC6066)</cell>
- <cell align="left" valign="middle"><em>PC</em></cell>
- <cell align="left" valign="middle"><em>22.2</em></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.2</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -1226,8 +1226,8 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">server_name (RFC6066)</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.2</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -1293,8 +1293,8 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">server_name (RFC6066)</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.2</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -1464,6 +1464,18 @@
</row>
<row>
<cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle">Arbitrary certificate chain orderings</cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>22.2</em></cell>
+ </row>
+ <row>
+ <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle">Extraneous certificates in chain</cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.2</em></cell>
+ </row>
+ <row>
+ <cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">status_request (RFC6066)</cell>
<cell align="left" valign="middle"><em>NC</em></cell>
<cell align="left" valign="middle"></cell>
@@ -1551,8 +1563,8 @@
extensions are used to guide certificate selection. As servers
MAY require the presence of the "server_name" extension, clients
SHOULD send this extension, when applicable.</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>PC</em></cell>
+ <cell align="left" valign="middle"><em>23.2</em></cell>
</row>
<row>
@@ -2028,8 +2040,8 @@
</url>
</cell>
<cell align="left" valign="middle"><em></em></cell>
- <cell align="left" valign="middle"><em>PC</em></cell>
- <cell align="left" valign="middle"><em>22</em></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.2</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -2070,8 +2082,8 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">Server Name Indication</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.2</em></cell>
</row>
<row>
@@ -2139,14 +2151,14 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle"><em>TLS 1.3 ServerHello</em></cell>
- <cell align="left" valign="middle"><em>PC</em></cell>
- <cell align="left" valign="middle"><em>22</em></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.2</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">MUST support the use of the "server_name" extension</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.2</em></cell>
</row>
<row>
diff --git a/lib/ssl/doc/src/using_ssl.xml b/lib/ssl/doc/src/using_ssl.xml
index f29606b419..4a66bf9d90 100644
--- a/lib/ssl/doc/src/using_ssl.xml
+++ b/lib/ssl/doc/src/using_ssl.xml
@@ -233,6 +233,182 @@ ssl:connect("localhost", 9999,
</section>
+
+ <section>
+ <title>Session Reuse pre TLS 1.3</title>
+ <p>Clients can request to reuse a session established
+ by a previous full handshake between that client and server by
+ sending the id of the session in the initial handshake
+ message. The server may or may not agree to reuse it. If agreed
+ the server will send back the id and if not it will send a new
+ id. The ssl application has several options for handling session
+ reuse.</p>
+
+ <p>On the client side the ssl application will save session data
+ to try to automate session reuse on behalf of the client processes
+ on the Erlang node. Note that only verified sessions will be
+ saved for security reasons, that is session resumption relies on
+ the certificate validation to have been run in the original
+ handshake. To minimize memory consumption only unique sessions
+ will be saved unless the special <c>save</c> value is specified
+ for the following option <c> {reuse_sessions, boolean() |
+ save}</c> in which case a full handhake will be performed and that
+ specific session will have been saved before the handshake
+ returns. The session id and even an opaque binary containing the
+ session data can be retrieved using
+ <c>ssl:connection_information/1</c> function. A saved session
+ (guaranteed by the save option) can be explicitly reused using
+ <c>{reuse_session, SessionId}</c>. Also it is possible for the
+ client to reuse a session that is not saved by the ssl application
+ using <c>{reuse_session, {SessionId, SessionData}}</c>.</p>
+
+ <note><p>When using explicit session reuse, it is up to the client
+ to make sure that the session being reused is for the correct
+ server and has been verified.</p></note>
+
+ <p>Here follows a client side example,
+ divide into several steps for readability.
+ </p>
+
+ <p>Step 1 - Automated Session Reuse</p>
+
+ <code type="erl">
+1> ssl:start().
+ok
+
+2&gt; {ok, C1} = ssl:connect("localhost", 9999, [{verify, verify_peer},
+ {versions, ['tlsv1.2']},
+ {cacertfile, "cacerts.pem"}]).
+{ok,{sslsocket,{gen_tcp,#Port&lt;0.7&gt;,tls_connection,undefined}, ...}}
+
+3&gt; ssl:connection_information(C1, [session_id]).
+{ok,[{session_id,&lt;&lt;95,32,43,22,35,63,249,22,26,36,106,
+ 152,49,52,124,56,130,192,137,161,
+ 146,145,164,232,...&gt;&gt;}]}
+
+%% Reuse session if possible, note that if C2 is really fast the session
+%% data might not be available for reuse.
+4&gt; {ok, C2} = ssl:connect("localhost", 9999, [{verify, verify_peer},
+ {versions, ['tlsv1.2']},
+ {cacertfile, "cacerts.pem"},
+ {reuse_sessions, true}]).
+{ok,{sslsocket,{gen_tcp,#Port&lt;0.8&gt;,tls_connection,undefined}, ...]}}
+
+%% C2 got same session ID as client one, session was automatically reused.
+5&gt; ssl:connection_information(C2, [session_id]).
+{ok,[{session_id,&lt;&lt;95,32,43,22,35,63,249,22,26,36,106,
+ 152,49,52,124,56,130,192,137,161,
+ 146,145,164,232,...&gt;&gt;}]}
+
+</code>
+
+<p>Step 2- Using <c>save</c> Option </p>
+
+<code type="erl">
+%% We want save this particular session for reuse although it has the same basis as C1
+6&gt; {ok, C3} = ssl:connect("localhost", 9999, [{verify, verify_peer},
+ {versions, ['tlsv1.2']},
+ {cacertfile, "cacerts.pem"},
+ {reuse_sessions, save}]).
+{ok,{sslsocket,{gen_tcp,#Port&lt;0.9&gt;,tls_connection,undefined}, ...]}}
+
+%% A full handshake is performed and we get a new session ID
+7&gt; {ok, [{session_id, ID}]} = ssl:connection_information(C3, [session_id]).
+{ok,[{session_id,&lt;&lt;91,84,27,151,183,39,84,90,143,141,
+ 121,190,66,192,10,1,27,192,33,95,78,
+ 8,34,180,...&gt;&gt;}]}
+
+%% Use automatic session reuse
+8&gt; {ok, C4} = ssl:connect("localhost", 9999, [{verify, verify_peer},
+ {versions, ['tlsv1.2']},
+ {cacertfile, "cacerts.pem"},
+ {reuse_sessions, true}]).
+{ok,{sslsocket,{gen_tcp,#Port&lt;0.10&gt;,tls_connection,
+ undefined}, ...]}}
+
+%% The "saved" one happened to be selected, but this is not a guarantee
+9&gt; ssl:connection_information(C4, [session_id]).
+{ok,[{session_id,&lt;&lt;91,84,27,151,183,39,84,90,143,141,
+ 121,190,66,192,10,1,27,192,33,95,78,
+ 8,34,180,...&gt;&gt;}]}
+
+%% Make sure to reuse the "saved" session
+10&gt; {ok, C5} = ssl:connect("localhost", 9999, [{verify, verify_peer},
+ {versions, ['tlsv1.2']},
+ {cacertfile, "cacerts.pem"},
+ {reuse_session, ID}]).
+{ok,{sslsocket,{gen_tcp,#Port&lt;0.11&gt;,tls_connection,
+ undefined}, ...]}}
+
+11&gt; ssl:connection_information(C5, [session_id]).
+{ok,[{session_id,&lt;&lt;91,84,27,151,183,39,84,90,143,141,
+ 121,190,66,192,10,1,27,192,33,95,78,
+ 8,34,180,...&gt;&gt;}]}
+</code>
+
+<p>Step 3 - Explicit Session Reuse </p>
+
+<code type="erl">
+%% Preform a full handshake and the session will not be saved for reuse
+12&gt; {ok, C9} = ssl:connect("localhost", 9999, [{verify, verify_peer},
+ {versions, ['tlsv1.2']},
+ {cacertfile, "cacerts.pem"},
+ {reuse_sessions, false},
+ {server_name_indication, disable}]).
+{ok,{sslsocket,{gen_tcp,#Port&lt;0.14&gt;,tls_connection, ...}}
+
+%% Fetch session ID and data for C9 connection
+12&gt; {ok, [{session_id, ID1}, {session_data, SessData}]} =
+ ssl:connection_information(C9, [session_id, session_data]).
+{ok,[{session_id,&lt;&lt;9,233,4,54,170,88,170,180,17,96,202,
+ 85,85,99,119,47,9,68,195,50,120,52,
+ 130,239,...&gt;&gt;},
+ {session_data,&lt;&lt;131,104,13,100,0,7,115,101,115,115,105,
+ 111,110,109,0,0,0,32,9,233,4,54,170,...&gt;&gt;}]}
+
+%% Explicitly reuse the session from C9
+13&gt; {ok, C10} = ssl:connect("localhost", 9999, [{verify, verify_peer},
+ {versions, ['tlsv1.2']},
+ {cacertfile, "cacerts.pem"},
+ {reuse_session, {ID1, SessData}}]).
+{ok,{sslsocket,{gen_tcp,#Port&lt;0.15&gt;,tls_connection,
+ undefined}, ...}}
+
+14&gt; ssl:connection_information(C10, [session_id]).
+{ok,[{session_id,&lt;&lt;9,233,4,54,170,88,170,180,17,96,202,
+ 85,85,99,119,47,9,68,195,50,120,52,
+ 130,239,...&gt;&gt;}]}
+
+</code>
+
+<p>Step 4 - Not Possible to Reuse Explicit Session by ID Only</p>
+
+<code type="erl">
+%% Try to reuse the session from C9 using only the id
+15&gt; {ok, E} = ssl:connect("localhost", 9999, [{verify, verify_peer},
+ {versions, ['tlsv1.2']},
+ {cacertfile, "cacerts.pem"},
+ {reuse_session, ID1}]).
+{ok,{sslsocket,{gen_tcp,#Port&lt;0.18&gt;,tls_connection,
+ undefined}, ...}}
+
+%% This will fail (as it is not saved for reuse)
+%% and a full handshake will be performed, we get a new id.
+16&gt; ssl:connection_information(E, [session_id]).
+{ok,[{session_id,&lt;&lt;87,46,43,126,175,68,160,153,37,29,
+ 196,240,65,160,254,88,65,224,18,63,
+ 18,17,174,39,...&gt;&gt;}]}
+</code>
+
+ <p>On the server side the the <c>{reuse_sessions, boolean()}</c> option
+ determines if the server will save session data and allow session
+ reuse or not. This can be further customized by the option
+ <c>{reuse_session, fun()}</c> that may introduce a local policy for
+ session reuse.
+ </p>
+
+ </section>
+
<section>
<title>Session Tickets and Session Resumption in TLS 1.3</title>
diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile
index b0f4cfd2da..1a55ee7b83 100644
--- a/lib/ssl/src/Makefile
+++ b/lib/ssl/src/Makefile
@@ -47,6 +47,7 @@ MODULES= \
dtls_connection \
dtls_connection_sup \
dtls_handshake \
+ dtls_gen_connection \
dtls_listener_sup \
dtls_packet_demux \
dtls_record \
@@ -65,7 +66,6 @@ MODULES= \
ssl_cipher \
ssl_cipher_format \
ssl_config \
- ssl_connection \
ssl_connection_sup \
ssl_crl \
ssl_crl_cache \
@@ -74,6 +74,7 @@ MODULES= \
ssl_dist_admin_sup \
ssl_dist_connection_sup \
ssl_dist_sup \
+ ssl_gen_statem \
ssl_handshake \
ssl_listen_tracker_sup \
ssl_logger \
@@ -84,19 +85,24 @@ MODULES= \
ssl_server_session_cache \
ssl_server_session_cache_db \
ssl_server_session_cache_sup \
+ ssl_upgrade_server_session_cache_sup \
ssl_session \
ssl_session_cache \
ssl_srp_primes \
ssl_sup \
tls_bloom_filter \
+ tls_dtls_connection \
tls_connection \
tls_connection_sup \
tls_connection_1_3 \
+ tls_gen_connection \
tls_handshake \
tls_handshake_1_3 \
tls_record \
tls_record_1_3 \
tls_client_ticket_store \
+ tls_dist_sup \
+ tls_dist_server_sup \
tls_sender \
tls_server_session_ticket\
tls_server_session_ticket_sup\
diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl
index 784eb20a92..fb389dcb4d 100644
--- a/lib/ssl/src/dtls_connection.erl
+++ b/lib/ssl/src/dtls_connection.erl
@@ -17,12 +17,105 @@
%%
%% %CopyrightEnd%
%%
+
-module(dtls_connection).
+%%----------------------------------------------------------------------
+%% Purpose: DTLS-1-DTLS-1.2 FSM (* = optional)
+%%----------------------------------------------------------------------
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% For UDP transport the following flights are used as retransmission units
+%% in case of package loss. Flight timers are handled in state entry functions.
+%%
+%% Client Server
+%% ------ ------
+%%
+%% ClientHello --------> Flight 1
+%%
+%% <------- HelloVerifyRequest Flight 2
+%%
+%% ClientHello --------> Flight 3
+%%
+%% ServerHello \
+%% Certificate* \
+%% ServerKeyExchange* Flight 4
+%% CertificateRequest* /
+%% <-------- ServerHelloDone /
+%%
+%% Certificate* \
+%% ClientKeyExchange \
+%% CertificateVerify* Flight 5
+%% [ChangeCipherSpec] /
+%% Finished --------> /
+%%
+%% [ChangeCipherSpec] \ Flight 6
+%% <-------- Finished /
+%%
+%% Message Flights for Full Handshake
+%%
+%%
+%% Client Server
+%% ------ ------
+%%
+%% ClientHello --------> Abbrev Flight 1
+%%
+%% ServerHello \ part 1
+%% [ChangeCipherSpec] Abbrev Flight 2
+%% <-------- Finished / part 2
+%%
+%% [ChangeCipherSpec] \ Abbrev Flight 3
+%% Finished --------> /
+%%
+%%
+%% Message Flights for Abbbriviated Handshake
+%%----------------------------------------------------------------------
+%% Start FSM ---> CONFIG_ERROR
+%% Send error to user
+%% | and shutdown
+%% |
+%% V
+%% INITIAL_HELLO
+%%
+%% | Send/ Recv Flight 1
+%% |
+%% |
+%% USER_HELLO |
+%% <- Possibly let user provide V
+%% options after looking at hello ex -> HELLO
+%% | Send Recv Flight 2 to Flight 4 or
+%% | Abbrev Flight 1 to Abbrev Flight 2 part 1
+%% |
+%% New session | Resumed session
+%% WAIT_OCSP_STAPELING CERTIFY <----------------------------------> ABBRIVIATED
+%%
+%% <- Possibly Receive -- | |
+%% OCSP Stapel ------> | Send/ Recv Flight 5 |
+%% | |
+%% V | Send / Recv Abbrev Flight part 2
+%% | to Abbrev Flight 3
+%% CIPHER |
+%% | |
+%% | Send/ Recv Flight 6 |
+%% | |
+%% V V
+%% ----------------------------------------------------
+%% |
+%% |
+%% V
+%% CONNECTION
+%% |
+%% | Renegotiaton
+%% V
+%% GO BACK TO HELLO
+%%----------------------------------------------------------------------
+
%% Internal application API
-behaviour(gen_statem).
+-include_lib("public_key/include/public_key.hrl").
+-include_lib("kernel/include/logger.hrl").
+
-include("dtls_connection.hrl").
-include("dtls_handshake.hrl").
-include("ssl_alert.hrl").
@@ -31,34 +124,31 @@
-include("ssl_api.hrl").
-include("ssl_internal.hrl").
-include("ssl_srp.hrl").
--include_lib("public_key/include/public_key.hrl").
--include_lib("kernel/include/logger.hrl").
%% Internal application API
%% Setup
--export([start_fsm/8, start_link/7, init/1, pids/1]).
+-export([init/1]).
-%% State transition handling
--export([next_event/3, next_event/4, handle_protocol_record/3]).
-
-%% Handshake handling
--export([renegotiate/2, send_handshake/2,
- queue_handshake/2, queue_change_cipher/2,
- reinit/1, reinit_handshake_data/1, select_sni_extension/1, empty_connection_state/2]).
-
-%% Alert and close handling
--export([encode_alert/3, send_alert/2, send_alert_in_connection/2, close/5, protocol_name/0]).
-
-%% Data handling
--export([socket/4, setopts/3, getopts/3]).
+-export([renegotiate/2]).
%% gen_statem state functions
--export([init/3, error/3, downgrade/3, %% Initiation and take down states
- hello/3, user_hello/3, wait_ocsp_stapling/3, certify/3, cipher/3, abbreviated/3, %% Handshake states
+-export([initial_hello/3,
+ config_error/3,
+ downgrade/3,
+ hello/3,
+ user_hello/3,
+ wait_ocsp_stapling/3,
+ certify/3,
+ cipher/3,
+ abbreviated/3,
connection/3]).
+
%% gen_statem callbacks
--export([callback_mode/0, terminate/3, code_change/4, format_status/2]).
+-export([callback_mode/0,
+ terminate/3,
+ code_change/4,
+ format_status/2]).
%%====================================================================
%% Internal application API
@@ -66,373 +156,47 @@
%%====================================================================
%% Setup
%%====================================================================
-start_fsm(Role, Host, Port, Socket, {#{erl_dist := false},_, Tracker} = Opts,
- User, {CbModule, _, _, _, _} = CbInfo,
- Timeout) ->
- try
- {ok, Pid} = dtls_connection_sup:start_child([Role, Host, Port, Socket,
- Opts, User, CbInfo]),
- {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, [Pid], CbModule, Tracker),
- ssl_connection:handshake(SslSocket, Timeout)
- catch
- error:{badmatch, {error, _} = Error} ->
- Error
- end.
-
-%%--------------------------------------------------------------------
--spec start_link(atom(), ssl:host(), inet:port_number(), port(), list(), pid(), tuple()) ->
- {ok, pid()} | ignore | {error, reason()}.
-%%
-%% Description: Creates a gen_statem process which calls Module:init/1 to
-%% initialize.
-%%--------------------------------------------------------------------
-start_link(Role, Host, Port, Socket, Options, User, CbInfo) ->
- {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Host, Port, Socket, Options, User, CbInfo]])}.
-
init([Role, Host, Port, Socket, Options, User, CbInfo]) ->
process_flag(trap_exit, true),
- State0 = #state{protocol_specific = Map} = initial_state(Role, Host, Port, Socket, Options, User, CbInfo),
+ State0 = #state{protocol_specific = Map} =
+ initial_state(Role, Host, Port, Socket, Options, User, CbInfo),
try
- State = ssl_connection:ssl_config(State0#state.ssl_options, Role, State0),
- gen_statem:enter_loop(?MODULE, [], init, State)
+ State = ssl_gen_statem:ssl_config(State0#state.ssl_options,
+ Role, State0),
+ gen_statem:enter_loop(?MODULE, [], initial_hello, State)
catch
throw:Error ->
- EState = State0#state{protocol_specific = Map#{error => Error}},
- gen_statem:enter_loop(?MODULE, [], error, EState)
+ EState = State0#state{protocol_specific =
+ Map#{error => Error}},
+ gen_statem:enter_loop(?MODULE, [], config_error, EState)
end.
-
-pids(_) ->
- [self()].
-
%%====================================================================
-%% State transition handling
+%% Handshake
%%====================================================================
-next_record(#state{handshake_env =
- #handshake_env{unprocessed_handshake_events = N} = HsEnv}
- = State) when N > 0 ->
- {no_record, State#state{handshake_env =
- HsEnv#handshake_env{unprocessed_handshake_events = N-1}}};
-next_record(#state{protocol_buffers =
- #protocol_buffers{dtls_cipher_texts = [#ssl_tls{epoch = Epoch} = CT | Rest]}
- = Buffers,
- connection_states = #{current_read := #{epoch := Epoch}} = ConnectionStates} = State) ->
- CurrentRead = dtls_record:get_connection_state_by_epoch(Epoch, ConnectionStates, read),
- case dtls_record:replay_detect(CT, CurrentRead) of
- false ->
- decode_cipher_text(State#state{connection_states = ConnectionStates}) ;
- true ->
- %% Ignore replayed record
- next_record(State#state{protocol_buffers =
- Buffers#protocol_buffers{dtls_cipher_texts = Rest},
- connection_states = ConnectionStates})
- end;
-next_record(#state{protocol_buffers =
- #protocol_buffers{dtls_cipher_texts = [#ssl_tls{epoch = Epoch} | Rest]}
- = Buffers,
- connection_states = #{current_read := #{epoch := CurrentEpoch}} = ConnectionStates} = State)
- when Epoch > CurrentEpoch ->
- %% TODO Buffer later Epoch message, drop it for now
- next_record(State#state{protocol_buffers =
- Buffers#protocol_buffers{dtls_cipher_texts = Rest},
- connection_states = ConnectionStates});
-next_record(#state{protocol_buffers =
- #protocol_buffers{dtls_cipher_texts = [ _ | Rest]}
- = Buffers,
- connection_states = ConnectionStates} = State) ->
- %% Drop old epoch message
- next_record(State#state{protocol_buffers =
- Buffers#protocol_buffers{dtls_cipher_texts = Rest},
- connection_states = ConnectionStates});
-next_record(#state{static_env = #static_env{role = server,
- socket = {Listener, {Client, _}}}} = State) ->
- dtls_packet_demux:active_once(Listener, Client, self()),
- {no_record, State};
-next_record(#state{protocol_specific = #{active_n_toggle := true,
- active_n := N} = ProtocolSpec,
- static_env = #static_env{role = client,
- socket = {_Server, Socket} = DTLSSocket,
- close_tag = CloseTag,
- transport_cb = Transport}} = State) ->
- case dtls_socket:setopts(Transport, Socket, [{active,N}]) of
- ok ->
- {no_record, State#state{protocol_specific =
- ProtocolSpec#{active_n_toggle => false}}};
- _ ->
- self() ! {CloseTag, DTLSSocket},
- {no_record, State}
- end;
-next_record(State) ->
- {no_record, State}.
-
-next_event(StateName, Record, State) ->
- next_event(StateName, Record, State, []).
-
-next_event(StateName, no_record,
- #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) ->
- case next_record(State0) of
- {no_record, State} ->
- ssl_connection:hibernate_after(StateName, State, Actions);
- {#ssl_tls{epoch = CurrentEpoch,
- type = ?HANDSHAKE,
- version = Version} = Record, State1} ->
- State = dtls_version(StateName, Version, State1),
- {next_state, StateName, State,
- [{next_event, internal, {protocol_record, Record}} | Actions]};
- {#ssl_tls{epoch = CurrentEpoch} = Record, State} ->
- {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]};
- {#ssl_tls{epoch = Epoch,
- type = ?HANDSHAKE,
- version = _Version}, State1} = _Record when Epoch == CurrentEpoch-1 ->
- {State, MoreActions} = send_handshake_flight(State1, CurrentEpoch),
- next_event(StateName, no_record, State, Actions ++ MoreActions);
- %% From FLIGHT perspective CHANGE_CIPHER_SPEC is treated as a handshake
- {#ssl_tls{epoch = Epoch,
- type = ?CHANGE_CIPHER_SPEC,
- version = _Version}, State1} = _Record when Epoch == CurrentEpoch-1 ->
- {State, MoreActions} = send_handshake_flight(State1, CurrentEpoch),
- next_event(StateName, no_record, State, Actions ++ MoreActions);
- {#ssl_tls{epoch = _Epoch,
- version = _Version}, State} ->
- %% TODO maybe buffer later epoch
- next_event(StateName, no_record, State, Actions);
- {#alert{} = Alert, State} ->
- Version = State#state.connection_env#connection_env.negotiated_version,
- handle_own_alert(Alert, Version, StateName, State)
- end;
-next_event(connection = StateName, Record,
- #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) ->
- case Record of
- #ssl_tls{epoch = CurrentEpoch,
- type = ?HANDSHAKE,
- version = Version} = Record ->
- State = dtls_version(StateName, Version, State0),
- {next_state, StateName, State,
- [{next_event, internal, {protocol_record, Record}} | Actions]};
- #ssl_tls{epoch = CurrentEpoch} ->
- {next_state, StateName, State0, [{next_event, internal, {protocol_record, Record}} | Actions]};
- #ssl_tls{epoch = Epoch,
- type = ?HANDSHAKE,
- version = _Version} when Epoch == CurrentEpoch-1 ->
- {State, MoreActions} = send_handshake_flight(State0, CurrentEpoch),
- next_event(StateName, no_record, State, Actions ++ MoreActions);
- %% From FLIGHT perspective CHANGE_CIPHER_SPEC is treated as a handshake
- #ssl_tls{epoch = Epoch,
- type = ?CHANGE_CIPHER_SPEC,
- version = _Version} when Epoch == CurrentEpoch-1 ->
- {State, MoreActions} = send_handshake_flight(State0, CurrentEpoch),
- next_event(StateName, no_record, State, Actions ++ MoreActions);
- _ ->
- next_event(StateName, no_record, State0, Actions)
- end;
-next_event(StateName, Record,
- #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) ->
- case Record of
- #ssl_tls{epoch = CurrentEpoch,
- version = Version} = Record ->
- State = dtls_version(StateName, Version, State0),
- {next_state, StateName, State,
- [{next_event, internal, {protocol_record, Record}} | Actions]};
- #ssl_tls{epoch = _Epoch,
- version = _Version} = _Record ->
- %% TODO maybe buffer later epoch
- next_event(StateName, no_record, State0, Actions);
- #alert{} = Alert ->
- Version = State0#state.connection_env#connection_env.negotiated_version,
- handle_own_alert(Alert, Version, StateName, State0)
- end.
-
-%%% DTLS record protocol level application data messages
-
-handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName0, State0) ->
- case ssl_connection:read_application_data(Data, State0) of
- {stop, _, _} = Stop->
- Stop;
- {Record, State1} ->
- {next_state, StateName, State, Actions} = next_event(StateName0, Record, State1),
- ssl_connection:hibernate_after(StateName, State, Actions)
- end;
-%%% DTLS record protocol level handshake messages
-handle_protocol_record(#ssl_tls{type = ?HANDSHAKE,
- fragment = Data},
- StateName,
- #state{protocol_buffers = Buffers0,
- connection_env = #connection_env{negotiated_version = Version},
- ssl_options = Options} = State) ->
- try
- case dtls_handshake:get_dtls_handshake(Version, Data, Buffers0, Options) of
- {[], Buffers} ->
- next_event(StateName, no_record, State#state{protocol_buffers = Buffers});
- {Packets, Buffers} ->
- HsEnv = State#state.handshake_env,
- Events = dtls_handshake_events(Packets),
- {next_state, StateName,
- State#state{protocol_buffers = Buffers,
- handshake_env =
- HsEnv#handshake_env{unprocessed_handshake_events
- = unprocessed_events(Events)}}, Events}
- end
- catch throw:#alert{} = Alert ->
- handle_own_alert(Alert, Version, StateName, State)
- end;
-%%% DTLS record protocol level change cipher messages
-handle_protocol_record(#ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, StateName, State) ->
- {next_state, StateName, State, [{next_event, internal, #change_cipher_spec{type = Data}}]};
-%%% DTLS record protocol level Alert messages
-handle_protocol_record(#ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName,
- #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
- case decode_alerts(EncAlerts) of
- Alerts = [_|_] ->
- handle_alerts(Alerts, {next_state, StateName, State});
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, StateName, State)
- end;
-%% Ignore unknown TLS record level protocol messages
-handle_protocol_record(#ssl_tls{type = _Unknown}, StateName, State) ->
- {next_state, StateName, State, []}.
-
-%%====================================================================
-%% Handshake handling
-%%====================================================================
-
renegotiate(#state{static_env = #static_env{role = client}} = State0, Actions) ->
%% Handle same way as if server requested
%% the renegotiation
- State = reinit_handshake_data(State0),
+ State = dtls_gen_connection:reinit_handshake_data(State0),
{next_state, connection, State,
[{next_event, internal, #hello_request{}} | Actions]};
renegotiate(#state{static_env = #static_env{role = server}} = State0, Actions) ->
HelloRequest = ssl_handshake:hello_request(),
State1 = prepare_flight(State0),
- {State, MoreActions} = send_handshake(HelloRequest, State1),
- next_event(hello, no_record, State, Actions ++ MoreActions).
-
-send_handshake(Handshake, #state{connection_states = ConnectionStates} = State) ->
- #{epoch := Epoch} = ssl_record:current_connection_state(ConnectionStates, write),
- send_handshake_flight(queue_handshake(Handshake, State), Epoch).
-
-queue_handshake(Handshake0, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv,
- connection_env = #connection_env{negotiated_version = Version},
- flight_buffer = #{handshakes := HsBuffer0,
- change_cipher_spec := undefined,
- next_sequence := Seq} = Flight0,
- ssl_options = #{log_level := LogLevel}} = State) ->
- Handshake = dtls_handshake:encode_handshake(Handshake0, Version, Seq),
- Hist = update_handshake_history(Handshake0, Handshake, Hist0),
- ssl_logger:debug(LogLevel, outbound, 'handshake', Handshake0),
-
- State#state{flight_buffer = Flight0#{handshakes => [Handshake | HsBuffer0],
- next_sequence => Seq +1},
- handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}};
-
-queue_handshake(Handshake0, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv,
- connection_env = #connection_env{negotiated_version = Version},
- flight_buffer = #{handshakes_after_change_cipher_spec := Buffer0,
- next_sequence := Seq} = Flight0,
- ssl_options = #{log_level := LogLevel}} = State) ->
- Handshake = dtls_handshake:encode_handshake(Handshake0, Version, Seq),
- Hist = update_handshake_history(Handshake0, Handshake, Hist0),
- ssl_logger:debug(LogLevel, outbound, 'handshake', Handshake0),
-
- State#state{flight_buffer = Flight0#{handshakes_after_change_cipher_spec => [Handshake | Buffer0],
- next_sequence => Seq +1},
- handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}}.
-
-queue_change_cipher(ChangeCipher, #state{flight_buffer = Flight,
- connection_states = ConnectionStates0} = State) ->
- ConnectionStates =
- dtls_record:next_epoch(ConnectionStates0, write),
- State#state{flight_buffer = Flight#{change_cipher_spec => ChangeCipher},
- connection_states = ConnectionStates}.
-
-reinit(State) ->
- %% To be API compatible with TLS NOOP here
- reinit_handshake_data(State).
-reinit_handshake_data(#state{static_env = #static_env{data_tag = DataTag},
- protocol_buffers = Buffers,
- protocol_specific = PS,
- handshake_env = HsEnv} = State) ->
- State#state{handshake_env = HsEnv#handshake_env{tls_handshake_history = ssl_handshake:init_handshake_history(),
- public_key_info = undefined,
- premaster_secret = undefined},
- protocol_specific = PS#{flight_state => initial_flight_state(DataTag)},
- flight_buffer = new_flight(),
- protocol_buffers =
- Buffers#protocol_buffers{
- dtls_handshake_next_seq = 0,
- dtls_handshake_next_fragments = [],
- dtls_handshake_later_fragments = []
- }}.
-
-select_sni_extension(#client_hello{extensions = #{sni := SNI}}) ->
- SNI;
-select_sni_extension(_) ->
- undefined.
-
-empty_connection_state(ConnectionEnd, BeastMitigation) ->
- Empty = ssl_record:empty_connection_state(ConnectionEnd, BeastMitigation),
- dtls_record:empty_connection_state(Empty).
-
-%%====================================================================
-%% Alert and close handling
-%%====================================================================
-encode_alert(#alert{} = Alert, Version, ConnectionStates) ->
- dtls_record:encode_alert_record(Alert, Version, ConnectionStates).
-
-send_alert(Alert, #state{static_env = #static_env{socket = Socket,
- transport_cb = Transport},
-
- connection_env = #connection_env{negotiated_version = Version},
- connection_states = ConnectionStates0,
- ssl_options = #{log_level := LogLevel}} = State0) ->
- {BinMsg, ConnectionStates} =
- encode_alert(Alert, Version, ConnectionStates0),
- send(Transport, Socket, BinMsg),
- ssl_logger:debug(LogLevel, outbound, 'record', BinMsg),
- State0#state{connection_states = ConnectionStates}.
-
-send_alert_in_connection(Alert, State) ->
- _ = send_alert(Alert, State),
- ok.
-
-close(downgrade, _,_,_,_) ->
- ok;
-%% Other
-close(_, Socket, Transport, _,_) ->
- dtls_socket:close(Transport,Socket).
-
-protocol_name() ->
- "DTLS".
-
-%%====================================================================
-%% Data handling
-%%====================================================================
-
-send(Transport, {Listener, Socket}, Data) when is_pid(Listener) -> % Server socket
- dtls_socket:send(Transport, Socket, Data);
-send(Transport, Socket, Data) -> % Client socket
- dtls_socket:send(Transport, Socket, Data).
-
-socket(Pid, Transport, Socket, _Tracker) ->
- dtls_socket:socket(Pid, Transport, Socket, ?MODULE).
-
-setopts(Transport, Socket, Other) ->
- dtls_socket:setopts(Transport, Socket, Other).
-
-getopts(Transport, Socket, Tag) ->
- dtls_socket:getopts(Transport, Socket, Tag).
+ {State, MoreActions} = dtls_gen_connection:send_handshake(HelloRequest, State1),
+ dtls_gen_connection:next_event(hello, no_record, State, Actions ++ MoreActions).
%%--------------------------------------------------------------------
%% State functions
%%--------------------------------------------------------------------
%%--------------------------------------------------------------------
--spec init(gen_statem:event_type(),
- {start, timeout()} | term(), #state{}) ->
- gen_statem:state_function_result().
+-spec initial_hello(gen_statem:event_type(),
+ {start, timeout()} | term(), #state{}) ->
+ gen_statem:state_function_result().
%%--------------------------------------------------------------------
-init(enter, _, State) ->
+initial_hello(enter, _, State) ->
{keep_state, State};
-init({call, From}, {start, Timeout},
+initial_hello({call, From}, {start, Timeout},
#state{static_env = #static_env{host = Host,
port = Port,
role = client,
@@ -441,12 +205,12 @@ init({call, From}, {start, Timeout},
handshake_env = #handshake_env{renegotiation = {Renegotiation, _}},
connection_env = CEnv,
ssl_options = #{versions := Versions} = SslOpts,
- session = #session{own_certificate = Cert} = NewSession,
+ session = #session{own_certificates = OwnCerts} = NewSession,
connection_states = ConnectionStates0
} = State0) ->
Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, NewSession),
Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
- Session#session.session_id, Renegotiation, Cert),
+ Session#session.session_id, Renegotiation, OwnCerts),
MaxFragEnum = maps:get(max_frag_enum, Hello#client_hello.extensions, undefined),
ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0),
@@ -454,38 +218,36 @@ init({call, From}, {start, Timeout},
HelloVersion = dtls_record:hello_version(Version, Versions),
State1 = prepare_flight(State0#state{connection_env = CEnv#connection_env{negotiated_version = Version},
connection_states = ConnectionStates1}),
- {State2, Actions} = send_handshake(Hello, State1#state{connection_env = CEnv#connection_env{negotiated_version = HelloVersion}}),
+ {State2, Actions} =
+ dtls_gen_connection:send_handshake(Hello,
+ State1#state{connection_env =
+ CEnv#connection_env{negotiated_version = HelloVersion}}),
State = State2#state{connection_env = CEnv#connection_env{negotiated_version = Version}, %% RequestedVersion
session = Session,
start_or_recv_from = From},
- next_event(hello, no_record, State, [{{timeout, handshake}, Timeout, close} | Actions]);
-init({call, _} = Type, Event, #state{static_env = #static_env{role = server},
- protocol_specific = PS} = State) ->
- Result = gen_handshake(?FUNCTION_NAME, Type, Event,
- State#state{protocol_specific = PS#{current_cookie_secret => dtls_v1:cookie_secret(),
+ dtls_gen_connection:next_event(hello, no_record, State, [{{timeout, handshake}, Timeout, close} | Actions]);
+initial_hello({call, _} = Type, Event, #state{static_env = #static_env{role = server},
+ protocol_specific = PS} = State) ->
+ Result = ssl_gen_statem:?FUNCTION_NAME(Type, Event,
+ State#state{protocol_specific =
+ PS#{current_cookie_secret => dtls_v1:cookie_secret(),
previous_cookie_secret => <<>>,
ignored_alerts => 0,
max_ignored_alerts => 10}}),
erlang:send_after(dtls_v1:cookie_timeout(), self(), new_cookie_secret),
Result;
-init(Type, Event, State) ->
- gen_handshake(?FUNCTION_NAME, Type, Event, State).
+initial_hello(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
%%--------------------------------------------------------------------
--spec error(gen_statem:event_type(),
- {start, timeout()} | term(), #state{}) ->
- gen_statem:state_function_result().
+-spec config_error(gen_statem:event_type(),
+ {start, timeout()} | term(), #state{}) ->
+ gen_statem:state_function_result().
%%--------------------------------------------------------------------
-error(enter, _, State) ->
+config_error(enter, _, State) ->
{keep_state, State};
-error({call, From}, {start, _Timeout},
- #state{protocol_specific = #{error := Error}} = State) ->
- {stop_and_reply, {shutdown, normal},
- [{reply, From, {error, Error}}], State};
-error({call, _} = Call, Msg, State) ->
- gen_handshake(?FUNCTION_NAME, Call, Msg, State);
-error(_, _, _) ->
- {keep_state_and_data, [postpone]}.
+config_error(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
%%--------------------------------------------------------------------
-spec hello(gen_statem:event_type(),
@@ -506,54 +268,67 @@ hello(internal, #client_hello{cookie = <<>>,
handshake_env = HsEnv,
connection_env = CEnv,
protocol_specific = #{current_cookie_secret := Secret}} = State0) ->
- {ok, {IP, Port}} = dtls_socket:peername(Transport, Socket),
- Cookie = dtls_handshake:cookie(Secret, IP, Port, Hello),
- %% FROM RFC 6347 regarding HelloVerifyRequest message:
- %% The server_version field has the same syntax as in TLS. However, in
- %% order to avoid the requirement to do version negotiation in the
- %% initial handshake, DTLS 1.2 server implementations SHOULD use DTLS
- %% version 1.0 regardless of the version of TLS that is expected to be
- %% negotiated.
- VerifyRequest = dtls_handshake:hello_verify_request(Cookie, ?HELLO_VERIFY_REQUEST_VERSION),
- State1 = prepare_flight(State0#state{connection_env = CEnv#connection_env{negotiated_version = Version}}),
- {State, Actions} = send_handshake(VerifyRequest, State1),
- next_event(?FUNCTION_NAME, no_record,
- State#state{handshake_env = HsEnv#handshake_env{
- tls_handshake_history =
- ssl_handshake:init_handshake_history()}},
- Actions);
-hello(internal, #hello_verify_request{cookie = Cookie}, #state{static_env = #static_env{role = client,
- host = Host,
- port = Port},
- handshake_env = #handshake_env{renegotiation = {Renegotiation, _},
- ocsp_stapling_state = OcspState0} = HsEnv,
- connection_env = CEnv,
- ssl_options = #{ocsp_stapling := OcspStaplingOpt,
- ocsp_nonce := OcspNonceOpt} = SslOpts,
- session = #session{own_certificate = Cert, session_id = Id},
- connection_states = ConnectionStates0
- } = State0) ->
+ case tls_dtls_connection:handle_sni_extension(State0, Hello) of
+ #state{} = State1 ->
+ {ok, {IP, Port}} = dtls_socket:peername(Transport, Socket),
+ Cookie = dtls_handshake:cookie(Secret, IP, Port, Hello),
+ %% FROM RFC 6347 regarding HelloVerifyRequest message:
+ %% The server_version field has the same syntax as in TLS. However, in
+ %% order to avoid the requirement to do version negotiation in the
+ %% initial handshake, DTLS 1.2 server implementations SHOULD use DTLS
+ %% version 1.0 regardless of the version of TLS that is expected to be
+ %% negotiated.
+ VerifyRequest = dtls_handshake:hello_verify_request(Cookie, ?HELLO_VERIFY_REQUEST_VERSION),
+ State2 = prepare_flight(State1#state{connection_env = CEnv#connection_env{negotiated_version = Version}}),
+ {State, Actions} = dtls_gen_connection:send_handshake(VerifyRequest, State2),
+ dtls_gen_connection:next_event(?FUNCTION_NAME, no_record,
+ State#state{handshake_env = HsEnv#handshake_env{
+ tls_handshake_history =
+ ssl_handshake:init_handshake_history()}},
+ Actions);
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version,?FUNCTION_NAME, State0)
+ end;
+hello(internal, #hello_verify_request{cookie = Cookie},
+ #state{static_env = #static_env{role = client,
+ host = Host,
+ port = Port},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _},
+ ocsp_stapling_state = OcspState0} = HsEnv,
+ connection_env = CEnv,
+ ssl_options = #{ocsp_stapling := OcspStaplingOpt,
+ ocsp_nonce := OcspNonceOpt} = SslOpts,
+ session = #session{own_certificates = OwnCerts,
+ session_id = Id},
+ connection_states = ConnectionStates0
+ } = State0) ->
OcspNonce = tls_handshake:ocsp_nonce(OcspNonceOpt, OcspStaplingOpt),
Hello = dtls_handshake:client_hello(Host, Port, Cookie, ConnectionStates0,
- SslOpts, Id, Renegotiation, Cert, OcspNonce),
+ SslOpts, Id, Renegotiation, OwnCerts, OcspNonce),
Version = Hello#client_hello.client_version,
State1 = prepare_flight(State0#state{handshake_env =
HsEnv#handshake_env{tls_handshake_history
= ssl_handshake:init_handshake_history(),
- ocsp_stapling_state = OcspState0#{ocsp_nonce => OcspNonce}}}),
+ ocsp_stapling_state =
+ OcspState0#{ocsp_nonce => OcspNonce}}}),
- {State2, Actions} = send_handshake(Hello, State1),
+ {State2, Actions} = dtls_gen_connection:send_handshake(Hello, State1),
State = State2#state{connection_env = CEnv#connection_env{negotiated_version = Version} % RequestedVersion
},
- next_event(?FUNCTION_NAME, no_record, State, Actions);
-hello(internal, #client_hello{extensions = Extensions} = Hello,
+ dtls_gen_connection:next_event(?FUNCTION_NAME, no_record, State, Actions);
+hello(internal, #client_hello{extensions = Extensions, client_version = ClientVersion} = Hello,
#state{ssl_options = #{handshake := hello},
handshake_env = HsEnv,
- start_or_recv_from = From} = State) ->
- {next_state, user_hello, State#state{start_or_recv_from = undefined,
- handshake_env = HsEnv#handshake_env{hello = Hello}},
- [{reply, From, {ok, Extensions}}]};
+ start_or_recv_from = From} = State0) ->
+ case tls_dtls_connection:handle_sni_extension(State0, Hello) of
+ #state{} = State ->
+ {next_state, user_hello, State#state{start_or_recv_from = undefined,
+ handshake_env = HsEnv#handshake_env{hello = Hello}},
+ [{reply, From, {ok, Extensions}}]};
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, ClientVersion, ?FUNCTION_NAME, State0)
+ end;
hello(internal, #server_hello{extensions = Extensions} = Hello,
#state{ssl_options = #{
handshake := hello},
@@ -568,18 +343,18 @@ hello(internal, #client_hello{cookie = Cookie} = Hello, #state{static_env = #sta
socket = Socket},
protocol_specific = #{current_cookie_secret := Secret,
previous_cookie_secret := PSecret}
- } = State0) ->
+ } = State) ->
{ok, {IP, Port}} = dtls_socket:peername(Transport, Socket),
case dtls_handshake:cookie(Secret, IP, Port, Hello) of
Cookie ->
- handle_client_hello(Hello, State0);
+ handle_client_hello(Hello, State);
_ ->
case dtls_handshake:cookie(PSecret, IP, Port, Hello) of
Cookie ->
- handle_client_hello(Hello, State0);
+ handle_client_hello(Hello, State);
_ ->
%% Handle bad cookie as new cookie request RFC 6347 4.1.2
- hello(internal, Hello#client_hello{cookie = <<>>}, State0)
+ hello(internal, Hello#client_hello{cookie = <<>>}, State)
end
end;
hello(internal, #server_hello{} = Hello,
@@ -594,12 +369,13 @@ hello(internal, #server_hello{} = Hello,
ssl_options = SslOptions} = State) ->
case dtls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation, OldId) of
#alert{} = Alert ->
- handle_own_alert(Alert, ReqVersion, ?FUNCTION_NAME, State);
+ ssl_gen_statem:handle_own_alert(Alert, ReqVersion, ?FUNCTION_NAME, State);
{Version, NewId, ConnectionStates, ProtoExt, Protocol, OcspState} ->
- ssl_connection:handle_session(Hello,
+ tls_dtls_connection:handle_session(Hello,
Version, NewId, ConnectionStates, ProtoExt, Protocol,
- State#state{handshake_env = HsEnv#handshake_env{
- ocsp_stapling_state = maps:merge(OcspState0,OcspState)}})
+ State#state{handshake_env =
+ HsEnv#handshake_env{
+ ocsp_stapling_state = maps:merge(OcspState0,OcspState)}})
end;
hello(internal, {handshake, {#client_hello{cookie = <<>>} = Handshake, _}}, State) ->
%% Initial hello should not be in handshake history
@@ -608,8 +384,9 @@ hello(internal, {handshake, {#hello_verify_request{} = Handshake, _}}, State) ->
%% hello_verify should not be in handshake history
{next_state, ?FUNCTION_NAME, State, [{next_event, internal, Handshake}]};
hello(internal, #change_cipher_spec{type = <<1>>}, State0) ->
- {State1, Actions0} = send_handshake_flight(State0, retransmit_epoch(?FUNCTION_NAME, State0)),
- {next_state, ?FUNCTION_NAME, State, Actions} = next_event(?FUNCTION_NAME, no_record, State1, Actions0),
+ {State1, Actions0} = dtls_gen_connection:send_handshake_flight(State0, retransmit_epoch(?FUNCTION_NAME, State0)),
+ {next_state, ?FUNCTION_NAME, State, Actions} =
+ dtls_gen_connection:next_event(?FUNCTION_NAME, no_record, State1, Actions0),
%% This will reset the retransmission timer by repeating the enter state event
{repeat_state, State, Actions};
hello(info, Event, State) ->
@@ -673,10 +450,11 @@ certify(enter, _, State0) ->
certify(info, Event, State) ->
gen_info(Event, ?FUNCTION_NAME, State);
certify(internal = Type, #server_hello_done{} = Event, State) ->
- ssl_connection:certify(Type, Event, prepare_flight(State), ?MODULE);
+ gen_handshake(?FUNCTION_NAME, Type, Event, prepare_flight(State));
certify(internal, #change_cipher_spec{type = <<1>>}, State0) ->
- {State1, Actions0} = send_handshake_flight(State0, retransmit_epoch(?FUNCTION_NAME, State0)),
- {next_state, ?FUNCTION_NAME, State, Actions} = next_event(?FUNCTION_NAME, no_record, State1, Actions0),
+ {State1, Actions0} = dtls_gen_connection:send_handshake_flight(State0, retransmit_epoch(?FUNCTION_NAME, State0)),
+ {next_state, ?FUNCTION_NAME, State, Actions} =
+ dtls_gen_connection:next_event(?FUNCTION_NAME, no_record, State1, Actions0),
%% This will reset the retransmission timer by repeating the enter state event
{repeat_state, State, Actions};
certify(state_timeout, Event, State) ->
@@ -697,17 +475,16 @@ cipher(internal = Type, #change_cipher_spec{type = <<1>>} = Event,
#state{connection_states = ConnectionStates0} = State) ->
ConnectionStates1 = dtls_record:save_current_connection_state(ConnectionStates0, read),
ConnectionStates = dtls_record:next_epoch(ConnectionStates1, read),
- ssl_connection:?FUNCTION_NAME(Type, Event, State#state{connection_states = ConnectionStates}, ?MODULE);
+ gen_handshake(?FUNCTION_NAME, Type, Event, State#state{connection_states = ConnectionStates});
cipher(internal = Type, #finished{} = Event, #state{connection_states = ConnectionStates,
protocol_specific = PS} = State) ->
- ssl_connection:?FUNCTION_NAME(Type, Event,
- prepare_flight(State#state{connection_states = ConnectionStates,
- protocol_specific = PS#{flight_state => connection}}),
- ?MODULE);
+ gen_handshake(?FUNCTION_NAME, Type, Event,
+ prepare_flight(State#state{connection_states = ConnectionStates,
+ protocol_specific = PS#{flight_state => connection}}));
cipher(state_timeout, Event, State) ->
handle_state_timeout(Event, ?FUNCTION_NAME, State);
cipher(Type, Event, State) ->
- ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE).
+ gen_handshake(?FUNCTION_NAME, Type, Event, State).
%%--------------------------------------------------------------------
-spec connection(gen_statem:event_type(),
@@ -726,7 +503,7 @@ connection(internal, #hello_request{}, #state{static_env = #static_env{host = Ho
},
handshake_env = #handshake_env{renegotiation = {Renegotiation, _}},
connection_env = CEnv,
- session = #session{own_certificate = Cert} = Session0,
+ session = #session{own_certificates = OwnCerts} = Session0,
ssl_options = #{versions := Versions} = SslOpts,
connection_states = ConnectionStates0,
protocol_specific = PS
@@ -734,16 +511,20 @@ connection(internal, #hello_request{}, #state{static_env = #static_env{host = Ho
Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, Session0),
Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
- Session#session.session_id, Renegotiation, Cert),
+ Session#session.session_id, Renegotiation, OwnCerts),
Version = Hello#client_hello.client_version,
HelloVersion = dtls_record:hello_version(Version, Versions),
State1 = prepare_flight(State0),
- {State2, Actions} = send_handshake(Hello, State1#state{connection_env = CEnv#connection_env{negotiated_version = HelloVersion}}),
- State = State2#state{protocol_specific = PS#{flight_state => initial_flight_state(DataTag)},
+ {State2, Actions} =
+ dtls_gen_connection:send_handshake(Hello,
+ State1#state{connection_env =
+ CEnv#connection_env{negotiated_version = HelloVersion}}),
+ State = State2#state{protocol_specific = PS#{flight_state => dtls_gen_connection:initial_flight_state(DataTag)},
session = Session},
- next_event(hello, no_record, State, Actions);
-connection(internal, #client_hello{} = Hello, #state{static_env = #static_env{role = server},
- handshake_env = #handshake_env{allow_renegotiate = true} = HsEnv} = State) ->
+ dtls_gen_connection:next_event(hello, no_record, State, Actions);
+connection(internal, #client_hello{} = Hello,
+ #state{static_env = #static_env{role = server},
+ handshake_env = #handshake_env{allow_renegotiate = true} = HsEnv} = State) ->
%% Mitigate Computational DoS attack
%% http://www.educatedguesswork.org/2011/10/ssltls_and_computational_dos.html
%% http://www.thc.org/thc-ssl-dos/ Rather than disabling client
@@ -753,20 +534,21 @@ connection(internal, #client_hello{} = Hello, #state{static_env = #static_env{ro
{next_state, hello, State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, peer},
allow_renegotiate = false}},
[{next_event, internal, Hello}]};
-connection(internal, #client_hello{}, #state{static_env = #static_env{role = server},
+connection(internal, #client_hello{}, #state{static_env = #static_env{role = server,
+ protocol_cb = Connection},
handshake_env = #handshake_env{allow_renegotiate = false}} = State0) ->
Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION),
- State1 = send_alert(Alert, State0),
- {Record, State} = ssl_connection:prepare_connection(State1, ?MODULE),
- next_event(?FUNCTION_NAME, Record, State);
+ State1 = dtls_gen_connection:send_alert(Alert, State0),
+ {Record, State} = ssl_gen_statem:prepare_connection(State1, Connection),
+ dtls_gen_connection:next_event(?FUNCTION_NAME, Record, State);
connection({call, From}, {application_data, Data}, State) ->
try
send_application_data(Data, From, ?FUNCTION_NAME, State)
catch throw:Error ->
- ssl_connection:hibernate_after(?FUNCTION_NAME, State, [{reply, From, Error}])
+ ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State, [{reply, From, Error}])
end;
connection(Type, Event, State) ->
- ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE).
+ tls_dtls_connection:?FUNCTION_NAME(Type, Event, State).
%%TODO does this make sense for DTLS ?
%%--------------------------------------------------------------------
@@ -776,7 +558,7 @@ connection(Type, Event, State) ->
downgrade(enter, _, State) ->
{keep_state, State};
downgrade(Type, Event, State) ->
- ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE).
+ tls_dtls_connection:?FUNCTION_NAME(Type, Event, State).
%%--------------------------------------------------------------------
%% gen_statem callbacks
@@ -785,13 +567,13 @@ callback_mode() ->
[state_functions, state_enter].
terminate(Reason, StateName, State) ->
- ssl_connection:terminate(Reason, StateName, State).
+ ssl_gen_statem:terminate(Reason, StateName, State).
code_change(_OldVsn, StateName, State, _Extra) ->
{ok, StateName, State}.
format_status(Type, Data) ->
- ssl_connection:format_status(Type, Data).
+ ssl_gen_statem:format_status(Type, Data).
%%--------------------------------------------------------------------
%%% Internal functions
@@ -818,7 +600,7 @@ initial_state(Role, Host, Port, Socket,
InitStatEnv = #static_env{
role = Role,
transport_cb = CbModule,
- protocol_cb = ?MODULE,
+ protocol_cb = dtls_gen_connection,
data_tag = DataTag,
close_tag = CloseTag,
error_tag = ErrorTag,
@@ -846,246 +628,95 @@ initial_state(Role, Host, Port, Socket,
protocol_buffers = #protocol_buffers{},
user_data_buffer = {[],0,[]},
start_or_recv_from = undefined,
- flight_buffer = new_flight(),
+ flight_buffer = dtls_gen_connection:new_flight(),
protocol_specific = #{active_n => InternalActiveN,
active_n_toggle => true,
- flight_state => initial_flight_state(DataTag)}
+ flight_state => dtls_gen_connection:initial_flight_state(DataTag)}
}.
-initial_flight_state(udp)->
- {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT};
-initial_flight_state(_) ->
- reliable.
-
-next_dtls_record(Data, StateName, #state{protocol_buffers = #protocol_buffers{
- dtls_record_buffer = Buf0,
- dtls_cipher_texts = CT0} = Buffers,
- connection_env = #connection_env{negotiated_version = Version},
- static_env = #static_env{data_tag = DataTag},
- ssl_options = SslOpts} = State0) ->
- case dtls_record:get_dtls_records(Data,
- {DataTag, StateName, Version,
- [dtls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_DATAGRAM_VERSIONS]},
- Buf0, SslOpts) of
- {Records, Buf1} ->
- CT1 = CT0 ++ Records,
- next_record(State0#state{protocol_buffers =
- Buffers#protocol_buffers{dtls_record_buffer = Buf1,
- dtls_cipher_texts = CT1}});
- #alert{} = Alert ->
- Alert
- end.
-
-dtls_handshake_events(Packets) ->
- lists:map(fun(Packet) ->
- {next_event, internal, {handshake, Packet}}
- end, Packets).
-
-decode_cipher_text(#state{protocol_buffers = #protocol_buffers{dtls_cipher_texts = [ CT | Rest]} = Buffers,
- connection_states = ConnStates0} = State) ->
- case dtls_record:decode_cipher_text(CT, ConnStates0) of
- {Plain, ConnStates} ->
- {Plain, State#state{protocol_buffers =
- Buffers#protocol_buffers{dtls_cipher_texts = Rest},
- connection_states = ConnStates}};
- #alert{} = Alert ->
- {Alert, State}
- end.
-
-dtls_version(hello, Version, #state{static_env = #static_env{role = server},
- connection_env = CEnv} = State) ->
- State#state{connection_env = CEnv#connection_env{negotiated_version = Version}}; %%Inital version
-dtls_version(_,_, State) ->
- State.
-
-handle_client_hello(#client_hello{client_version = ClientVersion} = Hello,
- #state{connection_states = ConnectionStates0,
- static_env = #static_env{trackers = Trackers},
- handshake_env = #handshake_env{kex_algorithm = KeyExAlg,
- renegotiation = {Renegotiation, _},
- negotiated_protocol = CurrentProtocol} = HsEnv,
- connection_env = CEnv,
- session = #session{own_certificate = Cert} = Session0,
- ssl_options = SslOpts} = State0) ->
- SessionTracker = proplists:get_value(session_id_tracker, Trackers),
- case dtls_handshake:hello(Hello, SslOpts, {SessionTracker, Session0,
- ConnectionStates0, Cert, KeyExAlg}, Renegotiation) of
- #alert{} = Alert ->
- handle_own_alert(Alert, ClientVersion, hello, State0);
- {Version, {Type, Session},
- ConnectionStates, Protocol0, ServerHelloExt, HashSign} ->
- Protocol = case Protocol0 of
- undefined -> CurrentProtocol;
- _ -> Protocol0
- end,
-
- State = prepare_flight(State0#state{connection_states = ConnectionStates,
- connection_env = CEnv#connection_env{negotiated_version = Version},
- handshake_env = HsEnv#handshake_env{
- hashsign_algorithm = HashSign,
+handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, State0) ->
+ case tls_dtls_connection:handle_sni_extension(State0, Hello) of
+ #state{connection_states = ConnectionStates0,
+ static_env = #static_env{trackers = Trackers},
+ handshake_env = #handshake_env{kex_algorithm = KeyExAlg,
+ renegotiation = {Renegotiation, _},
+ negotiated_protocol = CurrentProtocol} = HsEnv,
+ connection_env = CEnv,
+ session = #session{own_certificates = OwnCerts} = Session0,
+ ssl_options = SslOpts} = State1 ->
+ SessionTracker = proplists:get_value(session_id_tracker, Trackers),
+ case dtls_handshake:hello(Hello, SslOpts, {SessionTracker, Session0,
+ ConnectionStates0, OwnCerts, KeyExAlg}, Renegotiation) of
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, ClientVersion, hello, State1);
+ {Version, {Type, Session},
+ ConnectionStates, Protocol0, ServerHelloExt, HashSign} ->
+ Protocol = case Protocol0 of
+ undefined -> CurrentProtocol;
+ _ -> Protocol0
+ end,
+
+ State = prepare_flight(State0#state{connection_states = ConnectionStates,
+ connection_env = CEnv#connection_env{negotiated_version = Version},
+ handshake_env = HsEnv#handshake_env{
+ hashsign_algorithm = HashSign,
client_hello_version = ClientVersion,
- negotiated_protocol = Protocol},
- session = Session}),
-
- ssl_connection:hello(internal, {common_client_hello, Type, ServerHelloExt},
- State, ?MODULE)
- end.
-
-
-%% raw data from socket, unpack records
-handle_info({Protocol, _, _, _, Data}, StateName,
- #state{static_env = #static_env{role = Role,
- data_tag = Protocol}} = State0) ->
- case next_dtls_record(Data, StateName, State0) of
- {Record, State} ->
- next_event(StateName, Record, State);
- #alert{} = Alert ->
- ssl_connection:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State0),
- {stop, {shutdown, own_alert}, State0}
- end;
-
-handle_info({PassiveTag, Socket}, StateName,
- #state{static_env = #static_env{socket = {_, Socket},
- passive_tag = PassiveTag},
- protocol_specific = PS} = State) ->
- next_event(StateName, no_record,
- State#state{protocol_specific = PS#{active_n_toggle => true}});
-
-handle_info({CloseTag, Socket}, StateName,
- #state{static_env = #static_env{
- role = Role,
- socket = Socket,
- close_tag = CloseTag},
- connection_env = #connection_env{negotiated_version = Version},
- socket_options = #socket_options{active = Active},
- protocol_buffers = #protocol_buffers{dtls_cipher_texts = CTs},
- protocol_specific = PS} = State) ->
- %% Note that as of DTLS 1.2 (TLS 1.1),
- %% failure to properly close a connection no longer requires that a
- %% session not be resumed. This is a change from DTLS 1.0 to conform
- %% with widespread implementation practice.
- case (Active == false) andalso (CTs =/= []) of
- false ->
- case Version of
- {254, N} when N =< 253 ->
- ok;
- _ ->
- %% As invalidate_sessions here causes performance issues,
- %% we will conform to the widespread implementation
- %% practice and go aginst the spec
- %%invalidate_session(Role, Host, Port, Session)
- ok
- end,
- Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, transport_closed),
- ssl_connection:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State),
- {stop, {shutdown, transport_closed}, State};
- true ->
- %% Fixes non-delivery of final DTLS record in {active, once}.
- %% Basically allows the application the opportunity to set {active, once} again
- %% and then receive the final message.
- next_event(StateName, no_record, State#state{
- protocol_specific = PS#{active_n_toggle => true}})
- end;
+ negotiated_protocol = Protocol},
+ session = Session}),
+ {next_state, hello, State, [{next_event, internal, {common_client_hello, Type, ServerHelloExt}}]}
+ end;
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, ClientVersion, hello, State0)
+ end.
-handle_info(new_cookie_secret, StateName,
- #state{protocol_specific = #{current_cookie_secret := Secret} = CookieInfo} = State) ->
- erlang:send_after(dtls_v1:cookie_timeout(), self(), new_cookie_secret),
- {next_state, StateName, State#state{protocol_specific =
- CookieInfo#{current_cookie_secret => dtls_v1:cookie_secret(),
- previous_cookie_secret => Secret}}};
-handle_info(Msg, StateName, State) ->
- ssl_connection:StateName(info, Msg, State, ?MODULE).
handle_state_timeout(flight_retransmission_timeout, StateName,
#state{protocol_specific =
#{flight_state := {retransmit, _NextTimeout}}} = State0) ->
- {State1, Actions0} = send_handshake_flight(State0,
- retransmit_epoch(StateName, State0)),
- {next_state, StateName, State, Actions} = next_event(StateName, no_record, State1, Actions0),
+ {State1, Actions0} = dtls_gen_connection:send_handshake_flight(State0,
+ retransmit_epoch(StateName, State0)),
+ {next_state, StateName, State, Actions} =
+ dtls_gen_connection:next_event(StateName, no_record, State1, Actions0),
%% This will reset the retransmission timer by repeating the enter state event
{repeat_state, State, Actions}.
-handle_alerts([], Result) ->
- Result;
-handle_alerts(_, {stop, _, _} = Stop) ->
- Stop;
-handle_alerts([Alert | Alerts], {next_state, StateName, State}) ->
- handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State));
-handle_alerts([Alert | Alerts], {next_state, StateName, State, _Actions}) ->
- handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State)).
-
-handle_own_alert(Alert, Version, StateName,
- #state{static_env = #static_env{data_tag = udp,
- role = Role},
- ssl_options = #{log_level := LogLevel}} = State0) ->
- case ignore_alert(Alert, State0) of
- {true, State} ->
- log_ignore_alert(LogLevel, StateName, Alert, Role),
- {next_state, StateName, State};
- {false, State} ->
- ssl_connection:handle_own_alert(Alert, Version, StateName, State)
- end;
-handle_own_alert(Alert, Version, StateName, State) ->
- ssl_connection:handle_own_alert(Alert, Version, StateName, State).
-
-encode_handshake_flight(Flight, Version, MaxFragmentSize, Epoch, ConnectionStates) ->
- Fragments = lists:map(fun(Handshake) ->
- dtls_handshake:fragment_handshake(Handshake, MaxFragmentSize)
- end, Flight),
- dtls_record:encode_handshake(Fragments, Version, Epoch, ConnectionStates).
-encode_change_cipher(#change_cipher_spec{}, Version, Epoch, ConnectionStates) ->
- dtls_record:encode_change_cipher_spec(Version, Epoch, ConnectionStates).
-
-decode_alerts(Bin) ->
- ssl_alert:decode(Bin).
gen_handshake(StateName, Type, Event,
#state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
- try ssl_connection:StateName(Type, Event, State, ?MODULE) of
+ try tls_dtls_connection:StateName(Type, Event, State) of
Result ->
Result
catch
_:_ ->
- ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
malformed_handshake_data),
Version, StateName, State)
end.
gen_info(Event, connection = StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
- try handle_info(Event, StateName, State) of
+ try dtls_gen_connection:handle_info(Event, StateName, State) of
Result ->
Result
catch
_:_ ->
- ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR,
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR,
malformed_data),
Version, StateName, State)
end;
gen_info(Event, StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
- try handle_info(Event, StateName, State) of
+ try dtls_gen_connection:handle_info(Event, StateName, State) of
Result ->
Result
catch
_:_ ->
- ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
malformed_handshake_data),
Version, StateName, State)
end.
-unprocessed_events(Events) ->
- %% The first handshake event will be processed immediately
- %% as it is entered first in the event queue and
- %% when it is processed there will be length(Events)-1
- %% handshake events left to process before we should
- %% process more TLS-records received on the socket.
- erlang:length(Events)-1.
-update_handshake_history(#hello_verify_request{}, _, Hist) ->
- Hist;
-update_handshake_history(_, Handshake, Hist) ->
- ssl_handshake:update_handshake_history(Hist, iolist_to_binary(Handshake)).
prepare_flight(#state{flight_buffer = Flight,
connection_states = ConnectionStates0,
protocol_buffers =
@@ -1096,11 +727,6 @@ prepare_flight(#state{flight_buffer = Flight,
protocol_buffers = Buffers#protocol_buffers{
dtls_handshake_next_fragments = [],
dtls_handshake_later_fragments = []}}.
-new_flight() ->
- #{next_sequence => 0,
- handshakes => [],
- change_cipher_spec => undefined,
- handshakes_after_change_cipher_spec => []}.
next_flight(Flight) ->
Flight#{handshakes => [],
@@ -1126,133 +752,11 @@ new_timeout(N) when N =< 30000 ->
new_timeout(_) ->
60000.
-send_handshake_flight(#state{static_env = #static_env{socket = Socket,
- transport_cb = Transport},
- connection_env = #connection_env{negotiated_version = Version},
- flight_buffer = #{handshakes := Flight,
- change_cipher_spec := undefined},
- connection_states = ConnectionStates0,
- ssl_options = #{log_level := LogLevel}} = State0,
- Epoch) ->
- PMTUEstimate = 1400, %% TODO make configurable
- #{current_write := #{max_fragment_length := MaxFragmentLength}} = ConnectionStates0,
- MaxSize = min(MaxFragmentLength, PMTUEstimate),
- {Encoded, ConnectionStates} =
- encode_handshake_flight(lists:reverse(Flight), Version, MaxSize, Epoch, ConnectionStates0),
- send(Transport, Socket, Encoded),
- ssl_logger:debug(LogLevel, outbound, 'record', Encoded),
- {State0#state{connection_states = ConnectionStates}, []};
-
-send_handshake_flight(#state{static_env = #static_env{socket = Socket,
- transport_cb = Transport},
- connection_env = #connection_env{negotiated_version = Version},
- flight_buffer = #{handshakes := [_|_] = Flight0,
- change_cipher_spec := ChangeCipher,
- handshakes_after_change_cipher_spec := []},
- connection_states = ConnectionStates0,
- ssl_options = #{log_level := LogLevel}} = State0,
- Epoch) ->
- PMTUEstimate = 1400, %% TODO make configurable
- #{current_write := #{max_fragment_length := MaxFragmentLength}} = ConnectionStates0,
- MaxSize = min(MaxFragmentLength, PMTUEstimate),
- {HsBefore, ConnectionStates1} =
- encode_handshake_flight(lists:reverse(Flight0), Version, MaxSize, Epoch, ConnectionStates0),
- {EncChangeCipher, ConnectionStates} = encode_change_cipher(ChangeCipher, Version, Epoch, ConnectionStates1),
-
- send(Transport, Socket, [HsBefore, EncChangeCipher]),
- ssl_logger:debug(LogLevel, outbound, 'record', [HsBefore]),
- ssl_logger:debug(LogLevel, outbound, 'record', [EncChangeCipher]),
- {State0#state{connection_states = ConnectionStates}, []};
-
-send_handshake_flight(#state{static_env = #static_env{socket = Socket,
- transport_cb = Transport},
- connection_env = #connection_env{negotiated_version = Version},
- flight_buffer = #{handshakes := [_|_] = Flight0,
- change_cipher_spec := ChangeCipher,
- handshakes_after_change_cipher_spec := Flight1},
- connection_states = ConnectionStates0,
- ssl_options = #{log_level := LogLevel}} = State0,
- Epoch) ->
- PMTUEstimate = 1400, %% TODO make configurable
- #{current_write := #{max_fragment_length := MaxFragmentLength}} = ConnectionStates0,
- MaxSize = min(MaxFragmentLength, PMTUEstimate),
- {HsBefore, ConnectionStates1} =
- encode_handshake_flight(lists:reverse(Flight0), Version, MaxSize, Epoch-1, ConnectionStates0),
- {EncChangeCipher, ConnectionStates2} =
- encode_change_cipher(ChangeCipher, Version, Epoch-1, ConnectionStates1),
- {HsAfter, ConnectionStates} =
- encode_handshake_flight(lists:reverse(Flight1), Version, MaxSize, Epoch, ConnectionStates2),
- send(Transport, Socket, [HsBefore, EncChangeCipher, HsAfter]),
- ssl_logger:debug(LogLevel, outbound, 'record', [HsBefore]),
- ssl_logger:debug(LogLevel, outbound, 'record', [EncChangeCipher]),
- ssl_logger:debug(LogLevel, outbound, 'record', [HsAfter]),
- {State0#state{connection_states = ConnectionStates}, []};
-
-send_handshake_flight(#state{static_env = #static_env{socket = Socket,
- transport_cb = Transport},
- connection_env = #connection_env{negotiated_version = Version},
- flight_buffer = #{handshakes := [],
- change_cipher_spec := ChangeCipher,
- handshakes_after_change_cipher_spec := Flight1},
- connection_states = ConnectionStates0,
- ssl_options = #{log_level := LogLevel}} = State0,
- Epoch) ->
- PMTUEstimate = 1400, %% TODO make configurable
- #{current_write := #{max_fragment_length := MaxFragmentLength}} = ConnectionStates0,
- MaxSize = min(MaxFragmentLength, PMTUEstimate),
- {EncChangeCipher, ConnectionStates1} =
- encode_change_cipher(ChangeCipher, Version, Epoch-1, ConnectionStates0),
- {HsAfter, ConnectionStates} =
- encode_handshake_flight(lists:reverse(Flight1), Version, MaxSize, Epoch, ConnectionStates1),
- send(Transport, Socket, [EncChangeCipher, HsAfter]),
- ssl_logger:debug(LogLevel, outbound, 'record', [EncChangeCipher]),
- ssl_logger:debug(LogLevel, outbound, 'record', [HsAfter]),
- {State0#state{connection_states = ConnectionStates}, []}.
-
retransmit_epoch(_StateName, #state{connection_states = ConnectionStates}) ->
#{epoch := Epoch} =
ssl_record:current_connection_state(ConnectionStates, write),
Epoch.
-ignore_alert(#alert{level = ?FATAL}, #state{protocol_specific = #{ignored_alerts := N,
- max_ignored_alerts := N}} = State) ->
- {false, State};
-ignore_alert(#alert{level = ?FATAL} = Alert,
- #state{protocol_specific = #{ignored_alerts := N} = PS} = State) ->
- case is_ignore_alert(Alert) of
- true ->
- {true, State#state{protocol_specific = PS#{ignored_alerts => N+1}}};
- false ->
- {false, State}
- end;
-ignore_alert(_, State) ->
- {false, State}.
-
-%% RFC 6347 4.1.2.7. Handling Invalid Records
-%% recommends to silently ignore invalid DTLS records when
-%% upd is the transport. Note we do not support compression so no need
-%% include ?DECOMPRESSION_FAILURE
-is_ignore_alert(#alert{description = ?BAD_RECORD_MAC}) ->
- true;
-is_ignore_alert(#alert{description = ?RECORD_OVERFLOW}) ->
- true;
-is_ignore_alert(#alert{description = ?DECODE_ERROR}) ->
- true;
-is_ignore_alert(#alert{description = ?DECRYPT_ERROR}) ->
- true;
-is_ignore_alert(#alert{description = ?ILLEGAL_PARAMETER}) ->
- true;
-is_ignore_alert(_) ->
- false.
-
-log_ignore_alert(Level, StateName, #alert{where = Location} = Alert, Role) ->
- ssl_logger:log(info,
- Level, #{alert => Alert,
- alerter => ignored,
- statename => StateName,
- role => Role,
- protocol => protocol_name()}, Location).
-
send_application_data(Data, From, _StateName,
#state{static_env = #static_env{socket = Socket,
transport_cb = Transport},
@@ -1270,12 +774,12 @@ send_application_data(Data, From, _StateName,
{Msgs, ConnectionStates} =
dtls_record:encode_data(Data, Version, ConnectionStates0),
State = State0#state{connection_states = ConnectionStates},
- case send(Transport, Socket, Msgs) of
+ case dtls_gen_connection:send(Transport, Socket, Msgs) of
ok ->
ssl_logger:debug(LogLevel, outbound, 'record', Msgs),
- ssl_connection:hibernate_after(connection, State, [{reply, From, ok}]);
+ ssl_gen_statem:hibernate_after(connection, State, [{reply, From, ok}]);
Result ->
- ssl_connection:hibernate_after(connection, State, [{reply, From, Result}])
+ ssl_gen_statem:hibernate_after(connection, State, [{reply, From, Result}])
end
end.
diff --git a/lib/ssl/src/dtls_connection_sup.erl b/lib/ssl/src/dtls_connection_sup.erl
index 7d7be5743d..4c5c0a490f 100644
--- a/lib/ssl/src/dtls_connection_sup.erl
+++ b/lib/ssl/src/dtls_connection_sup.erl
@@ -57,10 +57,10 @@ init(_O) ->
MaxT = 3600,
Name = undefined, % As simple_one_for_one is used.
- StartFunc = {dtls_connection, start_link, []},
+ StartFunc = {ssl_gen_statem, start_link, []},
Restart = temporary, % E.g. should not be restarted
Shutdown = 4000,
- Modules = [dtls_connection, ssl_connection],
+ Modules = [ssl_gen_statem, dtls_connection],
Type = worker,
ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules},
diff --git a/lib/ssl/src/dtls_gen_connection.erl b/lib/ssl/src/dtls_gen_connection.erl
new file mode 100644
index 0000000000..2032d77074
--- /dev/null
+++ b/lib/ssl/src/dtls_gen_connection.erl
@@ -0,0 +1,685 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2020-2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+%%----------------------------------------------------------------------
+%% Purpose:
+%%----------------------------------------------------------------------
+-module(dtls_gen_connection).
+
+-include_lib("public_key/include/public_key.hrl").
+-include_lib("kernel/include/logger.hrl").
+
+-include("dtls_connection.hrl").
+-include("dtls_handshake.hrl").
+-include("ssl_alert.hrl").
+-include("dtls_record.hrl").
+-include("ssl_cipher.hrl").
+-include("ssl_api.hrl").
+-include("ssl_internal.hrl").
+
+%% Setup
+-export([start_fsm/8,
+ pids/1]).
+
+%% Handshake handling
+-export([send_handshake/2,
+ send_handshake_flight/2,
+ queue_handshake/2,
+ queue_change_cipher/2,
+ reinit/1,
+ reinit_handshake_data/1,
+ select_sni_extension/1,
+ empty_connection_state/2]).
+
+%% State transition handling
+-export([next_event/3,
+ next_event/4,
+ handle_protocol_record/3,
+ new_flight/0,
+ initial_flight_state/1
+ ]).
+
+%% Data handling
+-export([send/3,
+ socket/4,
+ setopts/3,
+ getopts/3,
+ handle_info/3]).
+
+%% Alert and close handling
+-export([send_alert/2,
+ send_alert_in_connection/2,
+ close/5,
+ protocol_name/0]).
+%%====================================================================
+%% Internal application API
+%%====================================================================
+%%====================================================================
+%% Setup
+%%====================================================================
+start_fsm(Role, Host, Port, Socket, {#{erl_dist := false},_, Tracker} = Opts,
+ User, {CbModule, _, _, _, _} = CbInfo,
+ Timeout) ->
+ try
+ {ok, Pid} = dtls_connection_sup:start_child([Role, Host, Port, Socket,
+ Opts, User, CbInfo]),
+ {ok, SslSocket} = ssl_gen_statem:socket_control(?MODULE, Socket, [Pid], CbModule, Tracker),
+ ssl_gen_statem:handshake(SslSocket, Timeout)
+ catch
+ error:{badmatch, {error, _} = Error} ->
+ Error
+ end.
+
+pids(_) ->
+ [self()].
+
+%%====================================================================
+%% State transition handling
+%%====================================================================
+next_record(#state{handshake_env =
+ #handshake_env{unprocessed_handshake_events = N} = HsEnv}
+ = State) when N > 0 ->
+ {no_record, State#state{handshake_env =
+ HsEnv#handshake_env{unprocessed_handshake_events = N-1}}};
+next_record(#state{protocol_buffers =
+ #protocol_buffers{dtls_cipher_texts = [#ssl_tls{epoch = Epoch} = CT | Rest]}
+ = Buffers,
+ connection_states = #{current_read := #{epoch := Epoch}} = ConnectionStates} = State) ->
+ CurrentRead = dtls_record:get_connection_state_by_epoch(Epoch, ConnectionStates, read),
+ case dtls_record:replay_detect(CT, CurrentRead) of
+ false ->
+ decode_cipher_text(State#state{connection_states = ConnectionStates}) ;
+ true ->
+ %% Ignore replayed record
+ next_record(State#state{protocol_buffers =
+ Buffers#protocol_buffers{dtls_cipher_texts = Rest},
+ connection_states = ConnectionStates})
+ end;
+next_record(#state{protocol_buffers =
+ #protocol_buffers{dtls_cipher_texts = [#ssl_tls{epoch = Epoch} | Rest]}
+ = Buffers,
+ connection_states = #{current_read := #{epoch := CurrentEpoch}} = ConnectionStates} = State)
+ when Epoch > CurrentEpoch ->
+ %% TODO Buffer later Epoch message, drop it for now
+ next_record(State#state{protocol_buffers =
+ Buffers#protocol_buffers{dtls_cipher_texts = Rest},
+ connection_states = ConnectionStates});
+next_record(#state{protocol_buffers =
+ #protocol_buffers{dtls_cipher_texts = [ _ | Rest]}
+ = Buffers,
+ connection_states = ConnectionStates} = State) ->
+ %% Drop old epoch message
+ next_record(State#state{protocol_buffers =
+ Buffers#protocol_buffers{dtls_cipher_texts = Rest},
+ connection_states = ConnectionStates});
+next_record(#state{static_env = #static_env{role = server,
+ socket = {Listener, {Client, _}}}} = State) ->
+ dtls_packet_demux:active_once(Listener, Client, self()),
+ {no_record, State};
+next_record(#state{protocol_specific = #{active_n_toggle := true,
+ active_n := N} = ProtocolSpec,
+ static_env = #static_env{role = client,
+ socket = {_Server, Socket} = DTLSSocket,
+ close_tag = CloseTag,
+ transport_cb = Transport}} = State) ->
+ case dtls_socket:setopts(Transport, Socket, [{active,N}]) of
+ ok ->
+ {no_record, State#state{protocol_specific =
+ ProtocolSpec#{active_n_toggle => false}}};
+ _ ->
+ self() ! {CloseTag, DTLSSocket},
+ {no_record, State}
+ end;
+next_record(State) ->
+ {no_record, State}.
+
+next_event(StateName, Record, State) ->
+ next_event(StateName, Record, State, []).
+
+next_event(StateName, no_record,
+ #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) ->
+ case next_record(State0) of
+ {no_record, State} ->
+ ssl_gen_statem:hibernate_after(StateName, State, Actions);
+ {#ssl_tls{epoch = CurrentEpoch,
+ type = ?HANDSHAKE,
+ version = Version} = Record, State1} ->
+ State = dtls_version(StateName, Version, State1),
+ {next_state, StateName, State,
+ [{next_event, internal, {protocol_record, Record}} | Actions]};
+ {#ssl_tls{epoch = CurrentEpoch} = Record, State} ->
+ {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]};
+ {#ssl_tls{epoch = Epoch,
+ type = ?HANDSHAKE,
+ version = _Version}, State1} = _Record when Epoch == CurrentEpoch-1 ->
+ {State, MoreActions} = send_handshake_flight(State1, CurrentEpoch),
+ next_event(StateName, no_record, State, Actions ++ MoreActions);
+ %% From FLIGHT perspective CHANGE_CIPHER_SPEC is treated as a handshake
+ {#ssl_tls{epoch = Epoch,
+ type = ?CHANGE_CIPHER_SPEC,
+ version = _Version}, State1} = _Record when Epoch == CurrentEpoch-1 ->
+ {State, MoreActions} = send_handshake_flight(State1, CurrentEpoch),
+ next_event(StateName, no_record, State, Actions ++ MoreActions);
+ {#ssl_tls{epoch = _Epoch,
+ version = _Version}, State} ->
+ %% TODO maybe buffer later epoch
+ next_event(StateName, no_record, State, Actions);
+ {#alert{} = Alert, State} ->
+ Version = State#state.connection_env#connection_env.negotiated_version,
+ handle_own_alert(Alert, Version, StateName, State)
+ end;
+next_event(connection = StateName, Record,
+ #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) ->
+ case Record of
+ #ssl_tls{epoch = CurrentEpoch,
+ type = ?HANDSHAKE,
+ version = Version} = Record ->
+ State = dtls_version(StateName, Version, State0),
+ {next_state, StateName, State,
+ [{next_event, internal, {protocol_record, Record}} | Actions]};
+ #ssl_tls{epoch = CurrentEpoch} ->
+ {next_state, StateName, State0, [{next_event, internal, {protocol_record, Record}} | Actions]};
+ #ssl_tls{epoch = Epoch,
+ type = ?HANDSHAKE,
+ version = _Version} when Epoch == CurrentEpoch-1 ->
+ {State, MoreActions} = send_handshake_flight(State0, CurrentEpoch),
+ next_event(StateName, no_record, State, Actions ++ MoreActions);
+ %% From FLIGHT perspective CHANGE_CIPHER_SPEC is treated as a handshake
+ #ssl_tls{epoch = Epoch,
+ type = ?CHANGE_CIPHER_SPEC,
+ version = _Version} when Epoch == CurrentEpoch-1 ->
+ {State, MoreActions} = send_handshake_flight(State0, CurrentEpoch),
+ next_event(StateName, no_record, State, Actions ++ MoreActions);
+ _ ->
+ next_event(StateName, no_record, State0, Actions)
+ end;
+next_event(StateName, Record,
+ #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) ->
+ case Record of
+ #ssl_tls{epoch = CurrentEpoch,
+ version = Version} = Record ->
+ State = dtls_version(StateName, Version, State0),
+ {next_state, StateName, State,
+ [{next_event, internal, {protocol_record, Record}} | Actions]};
+ #ssl_tls{epoch = _Epoch,
+ version = _Version} = _Record ->
+ %% TODO maybe buffer later epoch
+ next_event(StateName, no_record, State0, Actions);
+ #alert{} = Alert ->
+ Version = State0#state.connection_env#connection_env.negotiated_version,
+ handle_own_alert(Alert, Version, StateName, State0)
+ end.
+
+initial_flight_state(udp)->
+ {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT};
+initial_flight_state(_) ->
+ reliable.
+
+new_flight() ->
+ #{next_sequence => 0,
+ handshakes => [],
+ change_cipher_spec => undefined,
+ handshakes_after_change_cipher_spec => []}.
+
+send_handshake_flight(#state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
+ connection_env = #connection_env{negotiated_version = Version},
+ flight_buffer = #{handshakes := Flight,
+ change_cipher_spec := undefined},
+ connection_states = ConnectionStates0,
+ ssl_options = #{log_level := LogLevel}} = State0,
+ Epoch) ->
+ PMTUEstimate = 1400, %% TODO make configurable
+ #{current_write := #{max_fragment_length := MaxFragmentLength}} = ConnectionStates0,
+ MaxSize = min(MaxFragmentLength, PMTUEstimate),
+ {Encoded, ConnectionStates} =
+ encode_handshake_flight(lists:reverse(Flight), Version, MaxSize, Epoch, ConnectionStates0),
+ send(Transport, Socket, Encoded),
+ ssl_logger:debug(LogLevel, outbound, 'record', Encoded),
+ {State0#state{connection_states = ConnectionStates}, []};
+
+send_handshake_flight(#state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
+ connection_env = #connection_env{negotiated_version = Version},
+ flight_buffer = #{handshakes := [_|_] = Flight0,
+ change_cipher_spec := ChangeCipher,
+ handshakes_after_change_cipher_spec := []},
+ connection_states = ConnectionStates0,
+ ssl_options = #{log_level := LogLevel}} = State0,
+ Epoch) ->
+ PMTUEstimate = 1400, %% TODO make configurable
+ #{current_write := #{max_fragment_length := MaxFragmentLength}} = ConnectionStates0,
+ MaxSize = min(MaxFragmentLength, PMTUEstimate),
+ {HsBefore, ConnectionStates1} =
+ encode_handshake_flight(lists:reverse(Flight0), Version, MaxSize, Epoch, ConnectionStates0),
+ {EncChangeCipher, ConnectionStates} = encode_change_cipher(ChangeCipher, Version, Epoch, ConnectionStates1),
+
+ send(Transport, Socket, [HsBefore, EncChangeCipher]),
+ ssl_logger:debug(LogLevel, outbound, 'record', [HsBefore]),
+ ssl_logger:debug(LogLevel, outbound, 'record', [EncChangeCipher]),
+ {State0#state{connection_states = ConnectionStates}, []};
+
+send_handshake_flight(#state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
+ connection_env = #connection_env{negotiated_version = Version},
+ flight_buffer = #{handshakes := [_|_] = Flight0,
+ change_cipher_spec := ChangeCipher,
+ handshakes_after_change_cipher_spec := Flight1},
+ connection_states = ConnectionStates0,
+ ssl_options = #{log_level := LogLevel}} = State0,
+ Epoch) ->
+ PMTUEstimate = 1400, %% TODO make configurable
+ #{current_write := #{max_fragment_length := MaxFragmentLength}} = ConnectionStates0,
+ MaxSize = min(MaxFragmentLength, PMTUEstimate),
+ {HsBefore, ConnectionStates1} =
+ encode_handshake_flight(lists:reverse(Flight0), Version, MaxSize, Epoch-1, ConnectionStates0),
+ {EncChangeCipher, ConnectionStates2} =
+ encode_change_cipher(ChangeCipher, Version, Epoch-1, ConnectionStates1),
+ {HsAfter, ConnectionStates} =
+ encode_handshake_flight(lists:reverse(Flight1), Version, MaxSize, Epoch, ConnectionStates2),
+ send(Transport, Socket, [HsBefore, EncChangeCipher, HsAfter]),
+ ssl_logger:debug(LogLevel, outbound, 'record', [HsBefore]),
+ ssl_logger:debug(LogLevel, outbound, 'record', [EncChangeCipher]),
+ ssl_logger:debug(LogLevel, outbound, 'record', [HsAfter]),
+ {State0#state{connection_states = ConnectionStates}, []};
+
+send_handshake_flight(#state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
+ connection_env = #connection_env{negotiated_version = Version},
+ flight_buffer = #{handshakes := [],
+ change_cipher_spec := ChangeCipher,
+ handshakes_after_change_cipher_spec := Flight1},
+ connection_states = ConnectionStates0,
+ ssl_options = #{log_level := LogLevel}} = State0,
+ Epoch) ->
+ PMTUEstimate = 1400, %% TODO make configurable
+ #{current_write := #{max_fragment_length := MaxFragmentLength}} = ConnectionStates0,
+ MaxSize = min(MaxFragmentLength, PMTUEstimate),
+ {EncChangeCipher, ConnectionStates1} =
+ encode_change_cipher(ChangeCipher, Version, Epoch-1, ConnectionStates0),
+ {HsAfter, ConnectionStates} =
+ encode_handshake_flight(lists:reverse(Flight1), Version, MaxSize, Epoch, ConnectionStates1),
+ send(Transport, Socket, [EncChangeCipher, HsAfter]),
+ ssl_logger:debug(LogLevel, outbound, 'record', [EncChangeCipher]),
+ ssl_logger:debug(LogLevel, outbound, 'record', [HsAfter]),
+ {State0#state{connection_states = ConnectionStates}, []}.
+
+%%% DTLS record protocol level application data messages
+
+handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName0, State0) ->
+ case ssl_gen_statem:read_application_data(Data, State0) of
+ {stop, _, _} = Stop->
+ Stop;
+ {Record, State1} ->
+ {next_state, StateName, State, Actions} = next_event(StateName0, Record, State1),
+ ssl_gen_statem:hibernate_after(StateName, State, Actions)
+ end;
+%%% DTLS record protocol level handshake messages
+handle_protocol_record(#ssl_tls{type = ?HANDSHAKE,
+ fragment = Data},
+ StateName,
+ #state{protocol_buffers = Buffers0,
+ connection_env = #connection_env{negotiated_version = Version},
+ ssl_options = Options} = State) ->
+ try
+ case dtls_handshake:get_dtls_handshake(Version, Data, Buffers0, Options) of
+ {[], Buffers} ->
+ next_event(StateName, no_record, State#state{protocol_buffers = Buffers});
+ {Packets, Buffers} ->
+ HsEnv = State#state.handshake_env,
+ Events = dtls_handshake_events(Packets),
+ {next_state, StateName,
+ State#state{protocol_buffers = Buffers,
+ handshake_env =
+ HsEnv#handshake_env{unprocessed_handshake_events
+ = unprocessed_events(Events)}}, Events}
+ end
+ catch throw:#alert{} = Alert ->
+ handle_own_alert(Alert, Version, StateName, State)
+ end;
+%%% DTLS record protocol level change cipher messages
+handle_protocol_record(#ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, StateName, State) ->
+ {next_state, StateName, State, [{next_event, internal, #change_cipher_spec{type = Data}}]};
+%%% DTLS record protocol level Alert messages
+handle_protocol_record(#ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName,
+ #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
+ case decode_alerts(EncAlerts) of
+ Alerts = [_|_] ->
+ handle_alerts(Alerts, {next_state, StateName, State});
+ #alert{} = Alert ->
+ handle_own_alert(Alert, Version, StateName, State)
+ end;
+%% Ignore unknown TLS record level protocol messages
+handle_protocol_record(#ssl_tls{type = _Unknown}, StateName, State) ->
+ {next_state, StateName, State, []}.
+
+%%====================================================================
+%% Handshake handling
+%%====================================================================
+send_handshake(Handshake, #state{connection_states = ConnectionStates} = State) ->
+ #{epoch := Epoch} = ssl_record:current_connection_state(ConnectionStates, write),
+ send_handshake_flight(queue_handshake(Handshake, State), Epoch).
+
+queue_handshake(Handshake0, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv,
+ connection_env = #connection_env{negotiated_version = Version},
+ flight_buffer = #{handshakes := HsBuffer0,
+ change_cipher_spec := undefined,
+ next_sequence := Seq} = Flight0,
+ ssl_options = #{log_level := LogLevel}} = State) ->
+ Handshake = dtls_handshake:encode_handshake(Handshake0, Version, Seq),
+ Hist = update_handshake_history(Handshake0, Handshake, Hist0),
+ ssl_logger:debug(LogLevel, outbound, 'handshake', Handshake0),
+
+ State#state{flight_buffer = Flight0#{handshakes => [Handshake | HsBuffer0],
+ next_sequence => Seq +1},
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}};
+
+queue_handshake(Handshake0, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv,
+ connection_env = #connection_env{negotiated_version = Version},
+ flight_buffer = #{handshakes_after_change_cipher_spec := Buffer0,
+ next_sequence := Seq} = Flight0,
+ ssl_options = #{log_level := LogLevel}} = State) ->
+ Handshake = dtls_handshake:encode_handshake(Handshake0, Version, Seq),
+ Hist = update_handshake_history(Handshake0, Handshake, Hist0),
+ ssl_logger:debug(LogLevel, outbound, 'handshake', Handshake0),
+
+ State#state{flight_buffer = Flight0#{handshakes_after_change_cipher_spec => [Handshake | Buffer0],
+ next_sequence => Seq +1},
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}}.
+
+queue_change_cipher(ChangeCipher, #state{flight_buffer = Flight,
+ connection_states = ConnectionStates0} = State) ->
+ ConnectionStates =
+ dtls_record:next_epoch(ConnectionStates0, write),
+ State#state{flight_buffer = Flight#{change_cipher_spec => ChangeCipher},
+ connection_states = ConnectionStates}.
+
+reinit(State) ->
+ %% To be API compatible with TLS NOOP here
+ reinit_handshake_data(State).
+reinit_handshake_data(#state{static_env = #static_env{data_tag = DataTag},
+ protocol_buffers = Buffers,
+ protocol_specific = PS,
+ handshake_env = HsEnv} = State) ->
+ State#state{handshake_env = HsEnv#handshake_env{tls_handshake_history = ssl_handshake:init_handshake_history(),
+ public_key_info = undefined,
+ premaster_secret = undefined},
+ protocol_specific = PS#{flight_state => initial_flight_state(DataTag)},
+ flight_buffer = new_flight(),
+ protocol_buffers =
+ Buffers#protocol_buffers{
+ dtls_handshake_next_seq = 0,
+ dtls_handshake_next_fragments = [],
+ dtls_handshake_later_fragments = []
+ }}.
+
+select_sni_extension(#client_hello{extensions = #{sni := SNI}}) ->
+ SNI;
+select_sni_extension(_) ->
+ undefined.
+
+empty_connection_state(ConnectionEnd, BeastMitigation) ->
+ Empty = ssl_record:empty_connection_state(ConnectionEnd, BeastMitigation),
+ dtls_record:empty_connection_state(Empty).
+%%====================================================================
+%% Alert and close handling
+%%====================================================================
+encode_alert(#alert{} = Alert, Version, ConnectionStates) ->
+ dtls_record:encode_alert_record(Alert, Version, ConnectionStates).
+
+send_alert(Alert, #state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
+
+ connection_env = #connection_env{negotiated_version = Version},
+ connection_states = ConnectionStates0,
+ ssl_options = #{log_level := LogLevel}} = State0) ->
+ {BinMsg, ConnectionStates} =
+ encode_alert(Alert, Version, ConnectionStates0),
+ send(Transport, Socket, BinMsg),
+ ssl_logger:debug(LogLevel, outbound, 'record', BinMsg),
+ State0#state{connection_states = ConnectionStates}.
+
+send_alert_in_connection(Alert, State) ->
+ _ = send_alert(Alert, State),
+ ok.
+
+close(downgrade, _,_,_,_) ->
+ ok;
+%% Other
+close(_, Socket, Transport, _,_) ->
+ dtls_socket:close(Transport,Socket).
+
+protocol_name() ->
+ "DTLS".
+
+%%====================================================================
+%% Data handling
+%%====================================================================
+send(Transport, {Listener, Socket}, Data) when is_pid(Listener) ->
+ %% Server socket
+ dtls_socket:send(Transport, Socket, Data);
+send(Transport, Socket, Data) -> % Client socket
+ dtls_socket:send(Transport, Socket, Data).
+
+socket(Pid, Transport, Socket, _Tracker) ->
+ dtls_socket:socket(Pid, Transport, Socket, ?MODULE).
+
+setopts(Transport, Socket, Other) ->
+ dtls_socket:setopts(Transport, Socket, Other).
+
+getopts(Transport, Socket, Tag) ->
+ dtls_socket:getopts(Transport, Socket, Tag).
+
+%% raw data from socket, unpack records
+handle_info({Protocol, _, _, _, Data}, StateName,
+ #state{static_env = #static_env{role = Role,
+ data_tag = Protocol}} = State0) ->
+ case next_dtls_record(Data, StateName, State0) of
+ {Record, State} ->
+ next_event(StateName, Record, State);
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State0),
+ {stop, {shutdown, own_alert}, State0}
+ end;
+
+handle_info({PassiveTag, Socket}, StateName,
+ #state{static_env = #static_env{socket = {_, Socket},
+ passive_tag = PassiveTag},
+ protocol_specific = PS} = State) ->
+ next_event(StateName, no_record,
+ State#state{protocol_specific = PS#{active_n_toggle => true}});
+
+handle_info({CloseTag, Socket}, StateName,
+ #state{static_env = #static_env{
+ role = Role,
+ socket = Socket,
+ close_tag = CloseTag},
+ connection_env = #connection_env{negotiated_version = Version},
+ socket_options = #socket_options{active = Active},
+ protocol_buffers = #protocol_buffers{dtls_cipher_texts = CTs},
+ protocol_specific = PS} = State) ->
+ %% Note that as of DTLS 1.2 (TLS 1.1),
+ %% failure to properly close a connection no longer requires that a
+ %% session not be resumed. This is a change from DTLS 1.0 to conform
+ %% with widespread implementation practice.
+ case (Active == false) andalso (CTs =/= []) of
+ false ->
+ case Version of
+ {254, N} when N =< 253 ->
+ ok;
+ _ ->
+ %% As invalidate_sessions here causes performance issues,
+ %% we will conform to the widespread implementation
+ %% practice and go aginst the spec
+ %%invalidate_session(Role, Host, Port, Session)
+ ok
+ end,
+ Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, transport_closed),
+ ssl_gen_statem:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State),
+ {stop, {shutdown, transport_closed}, State};
+ true ->
+ %% Fixes non-delivery of final DTLS record in {active, once}.
+ %% Basically allows the application the opportunity to set {active, once} again
+ %% and then receive the final message.
+ next_event(StateName, no_record, State#state{
+ protocol_specific = PS#{active_n_toggle => true}})
+ end;
+
+handle_info(new_cookie_secret, StateName,
+ #state{protocol_specific = #{current_cookie_secret := Secret} = CookieInfo} = State) ->
+ erlang:send_after(dtls_v1:cookie_timeout(), self(), new_cookie_secret),
+ {next_state, StateName, State#state{protocol_specific =
+ CookieInfo#{current_cookie_secret => dtls_v1:cookie_secret(),
+ previous_cookie_secret => Secret}}};
+handle_info(Msg, StateName, State) ->
+ ssl_gen_statem:handle_info(Msg, StateName, State).
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
+
+dtls_handshake_events(Packets) ->
+ lists:map(fun(Packet) ->
+ {next_event, internal, {handshake, Packet}}
+ end, Packets).
+
+unprocessed_events(Events) ->
+ %% The first handshake event will be processed immediately
+ %% as it is entered first in the event queue and
+ %% when it is processed there will be length(Events)-1
+ %% handshake events left to process before we should
+ %% process more TLS-records received on the socket.
+ erlang:length(Events)-1.
+
+encode_handshake_flight(Flight, Version, MaxFragmentSize, Epoch, ConnectionStates) ->
+ Fragments = lists:map(fun(Handshake) ->
+ dtls_handshake:fragment_handshake(Handshake, MaxFragmentSize)
+ end, Flight),
+ dtls_record:encode_handshake(Fragments, Version, Epoch, ConnectionStates).
+
+encode_change_cipher(#change_cipher_spec{}, Version, Epoch, ConnectionStates) ->
+ dtls_record:encode_change_cipher_spec(Version, Epoch, ConnectionStates).
+
+update_handshake_history(#hello_verify_request{}, _, Hist) ->
+ Hist;
+update_handshake_history(_, Handshake, Hist) ->
+ ssl_handshake:update_handshake_history(Hist, iolist_to_binary(Handshake)).
+
+next_dtls_record(Data, StateName, #state{protocol_buffers = #protocol_buffers{
+ dtls_record_buffer = Buf0,
+ dtls_cipher_texts = CT0} = Buffers,
+ connection_env = #connection_env{negotiated_version = Version},
+ static_env = #static_env{data_tag = DataTag},
+ ssl_options = SslOpts} = State0) ->
+ case dtls_record:get_dtls_records(Data,
+ {DataTag, StateName, Version,
+ [dtls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_DATAGRAM_VERSIONS]},
+ Buf0, SslOpts) of
+ {Records, Buf1} ->
+ CT1 = CT0 ++ Records,
+ next_record(State0#state{protocol_buffers =
+ Buffers#protocol_buffers{dtls_record_buffer = Buf1,
+ dtls_cipher_texts = CT1}});
+ #alert{} = Alert ->
+ Alert
+ end.
+
+
+
+decode_cipher_text(#state{protocol_buffers = #protocol_buffers{dtls_cipher_texts = [ CT | Rest]} = Buffers,
+ connection_states = ConnStates0} = State) ->
+ case dtls_record:decode_cipher_text(CT, ConnStates0) of
+ {Plain, ConnStates} ->
+ {Plain, State#state{protocol_buffers =
+ Buffers#protocol_buffers{dtls_cipher_texts = Rest},
+ connection_states = ConnStates}};
+ #alert{} = Alert ->
+ {Alert, State}
+ end.
+
+decode_alerts(Bin) ->
+ ssl_alert:decode(Bin).
+
+handle_alerts([], Result) ->
+ Result;
+handle_alerts(_, {stop, _, _} = Stop) ->
+ Stop;
+handle_alerts([Alert | Alerts], {next_state, StateName, State}) ->
+ handle_alerts(Alerts, ssl_gen_statem:handle_alert(Alert, StateName, State));
+handle_alerts([Alert | Alerts], {next_state, StateName, State, _Actions}) ->
+ handle_alerts(Alerts, ssl_gen_statem:handle_alert(Alert, StateName, State)).
+
+handle_own_alert(Alert, Version, StateName,
+ #state{static_env = #static_env{data_tag = udp,
+ role = Role},
+ ssl_options = #{log_level := LogLevel}} = State0) ->
+ case ignore_alert(Alert, State0) of
+ {true, State} ->
+ log_ignore_alert(LogLevel, StateName, Alert, Role),
+ {next_state, StateName, State};
+ {false, State} ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, StateName, State)
+ end;
+handle_own_alert(Alert, Version, StateName, State) ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, StateName, State).
+ignore_alert(#alert{level = ?FATAL}, #state{protocol_specific = #{ignored_alerts := N,
+ max_ignored_alerts := N}} = State) ->
+ {false, State};
+ignore_alert(#alert{level = ?FATAL} = Alert,
+ #state{protocol_specific = #{ignored_alerts := N} = PS} = State) ->
+ case is_ignore_alert(Alert) of
+ true ->
+ {true, State#state{protocol_specific = PS#{ignored_alerts => N+1}}};
+ false ->
+ {false, State}
+ end;
+ignore_alert(_, State) ->
+ {false, State}.
+
+%% RFC 6347 4.1.2.7. Handling Invalid Records
+%% recommends to silently ignore invalid DTLS records when
+%% upd is the transport. Note we do not support compression so no need
+%% include ?DECOMPRESSION_FAILURE
+is_ignore_alert(#alert{description = ?BAD_RECORD_MAC}) ->
+ true;
+is_ignore_alert(#alert{description = ?RECORD_OVERFLOW}) ->
+ true;
+is_ignore_alert(#alert{description = ?DECODE_ERROR}) ->
+ true;
+is_ignore_alert(#alert{description = ?DECRYPT_ERROR}) ->
+ true;
+is_ignore_alert(#alert{description = ?ILLEGAL_PARAMETER}) ->
+ true;
+is_ignore_alert(_) ->
+ false.
+
+log_ignore_alert(Level, StateName, #alert{where = Location} = Alert, Role) ->
+ ssl_logger:log(info,
+ Level, #{alert => Alert,
+ alerter => ignored,
+ statename => StateName,
+ role => Role,
+ protocol => protocol_name()}, Location).
+
+dtls_version(hello, Version, #state{static_env = #static_env{role = server},
+ connection_env = CEnv} = State) ->
+ State#state{connection_env = CEnv#connection_env{negotiated_version = Version}}; %%Inital version
+dtls_version(_,_, State) ->
+ State.
diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl
index 9c1cb8ca8c..af053ef48c 100644
--- a/lib/ssl/src/dtls_handshake.erl
+++ b/lib/ssl/src/dtls_handshake.erl
@@ -47,20 +47,20 @@
%%====================================================================
%%--------------------------------------------------------------------
-spec client_hello(ssl:host(), inet:port_number(), ssl_record:connection_states(),
- ssl_options(), binary(), boolean(), der_cert()) ->
+ ssl_options(), binary(), boolean(), [der_cert()]) ->
#client_hello{}.
%%
%% Description: Creates a client hello message.
%%--------------------------------------------------------------------
client_hello(Host, Port, ConnectionStates, SslOpts,
- Id, Renegotiation, OwnCert) ->
+ Id, Renegotiation, OwnCerts) ->
%% First client hello (two sent in DTLS ) uses empty Cookie
client_hello(Host, Port, <<>>, ConnectionStates, SslOpts,
- Id, Renegotiation, OwnCert, undefined).
+ Id, Renegotiation, OwnCerts, undefined).
%%--------------------------------------------------------------------
-spec client_hello(ssl:host(), inet:port_number(), term(), ssl_record:connection_states(),
- ssl_options(), binary(),boolean(), der_cert(), binary() | undefined) ->
+ ssl_options(), binary(),boolean(), [der_cert()], binary() | undefined) ->
#client_hello{}.
%%
%% Description: Creates a client hello message.
@@ -174,27 +174,28 @@ handle_client_hello(Version,
signature_algs := SupportedHashSigns,
eccs := SupportedECCs,
honor_ecc_order := ECCOrder} = SslOpts,
- {SessIdTracker, Session0, ConnectionStates0, Cert, _},
+ {SessIdTracker, Session0, ConnectionStates0, OwnCerts, _},
Renegotiation) ->
+ OwnCert = ssl_handshake:select_own_cert(OwnCerts),
case dtls_record:is_acceptable_version(Version, Versions) of
true ->
Curves = maps:get(elliptic_curves, HelloExt, undefined),
ClientHashSigns = maps:get(signature_algs, HelloExt, undefined),
TLSVersion = dtls_v1:corresponding_tls_version(Version),
AvailableHashSigns = ssl_handshake:available_signature_algs(
- ClientHashSigns, SupportedHashSigns, Cert,TLSVersion),
+ ClientHashSigns, SupportedHashSigns, OwnCert,TLSVersion),
ECCCurve = ssl_handshake:select_curve(Curves, SupportedECCs, ECCOrder),
{Type, #session{cipher_suite = CipherSuite} = Session1}
= ssl_handshake:select_session(SugesstedId, CipherSuites,
AvailableHashSigns, Compressions,
SessIdTracker, Session0#session{ecc = ECCCurve}, TLSVersion,
- SslOpts, Cert),
+ SslOpts, OwnCert),
case CipherSuite of
no_suite ->
?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY);
_ ->
#{key_exchange := KeyExAlg} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
- case ssl_handshake:select_hashsign({ClientHashSigns, undefined}, Cert, KeyExAlg,
+ case ssl_handshake:select_hashsign({ClientHashSigns, undefined}, OwnCert, KeyExAlg,
SupportedHashSigns, TLSVersion) of
#alert{} = Alert ->
Alert;
diff --git a/lib/ssl/src/dtls_listener_sup.erl b/lib/ssl/src/dtls_listener_sup.erl
index ab1c5eee20..4f46407290 100644
--- a/lib/ssl/src/dtls_listener_sup.erl
+++ b/lib/ssl/src/dtls_listener_sup.erl
@@ -30,8 +30,8 @@
%% API
-export([start_link/0]).
-export([start_child/1,
- lookup_listner/1,
- register_listner/2]).
+ lookup_listener/2,
+ register_listener/3]).
%% Supervisor callback
-export([init/1]).
@@ -44,32 +44,34 @@ start_link() ->
start_child(Args) ->
supervisor:start_child(?MODULE, Args).
-
-lookup_listner(0) ->
- undefined;
-lookup_listner(Port) ->
- try ets:lookup(dtls_listener_sup, Port) of
- [{Port, {Owner, Handler}}] ->
+
+lookup_listener(IP, Port) ->
+ try ets:lookup(dtls_listener_sup, {IP, Port}) of
+ [] ->
+ undefined;
+ [{{IP, Port}, {Owner, Handler}}] ->
case erlang:is_process_alive(Handler) of
true ->
- case (Owner =/= undefined) andalso erlang:is_process_alive(Owner) of
+ case (Owner =/= undefined) andalso
+ erlang:is_process_alive(Owner) of
true ->
+ %% Trying to bind port that is already bound
{error, already_listening};
false ->
+ %% Re-open same listen socket when the handler
+ %% is dead.
{ok, Handler}
end;
false ->
- ets:delete(dtls_listener_sup, Port),
+ ets:delete(dtls_listener_sup, {IP, Port}),
undefined
- end;
- [] ->
- undefined
+ end
catch _:_ ->
undefined
end.
-register_listner(OwnerAndListner, Port) ->
- ets:insert(dtls_listener_sup, {Port, OwnerAndListner}).
+register_listener(OwnerAndListner, IP, Port) ->
+ ets:insert(dtls_listener_sup, {{IP, Port}, OwnerAndListner}).
%%%=========================================================================
%%% Supervisor callback
diff --git a/lib/ssl/src/dtls_packet_demux.erl b/lib/ssl/src/dtls_packet_demux.erl
index 234d298989..f9660ea1b6 100644
--- a/lib/ssl/src/dtls_packet_demux.erl
+++ b/lib/ssl/src/dtls_packet_demux.erl
@@ -102,38 +102,19 @@ getstat(PacketSocket, Opts) ->
%%% gen_server callbacks
%%%===================================================================
-init([Port0, {TransportModule, _,_,_,_} = TransportInfo, EmOpts, InetOptions, DTLSOptions]) ->
- try
- {ok, Socket} = TransportModule:open(Port0, InetOptions),
- InternalActiveN = case application:get_env(ssl, internal_active_n) of
- {ok, N} when is_integer(N) ->
- N;
- _ ->
- ?INTERNAL_ACTIVE_N
- end,
-
- Port = case Port0 of
- 0 ->
- {ok, P} = inet:port(Socket),
- P;
- _ ->
- Port0
- end,
-
- {ok, SessionIdHandle} = session_id_tracker(DTLSOptions),
-
- {ok, #state{active_n = InternalActiveN,
- port = Port,
- first = true,
- transport = TransportInfo,
- dtls_options = DTLSOptions,
- emulated_options = EmOpts,
- listener = Socket,
- close = false,
- session_id_tracker = SessionIdHandle}}
- catch _:_ ->
- {stop, {shutdown, {error, closed}}}
- end.
+init([Port0, TransportInfo, EmOpts, DTLSOptions, Socket]) ->
+ InternalActiveN = get_internal_active_n(),
+ {ok, SessionIdHandle} = session_id_tracker(Socket, DTLSOptions),
+ {ok, #state{active_n = InternalActiveN,
+ port = Port0,
+ first = true,
+ transport = TransportInfo,
+ dtls_options = DTLSOptions,
+ emulated_options = EmOpts,
+ listener = Socket,
+ close = false,
+ session_id_tracker = SessionIdHandle}}.
+
handle_call({accept, _}, _, #state{close = true} = State) ->
{reply, {error, closed}, State};
@@ -374,5 +355,14 @@ emulated_opts_list(Opts, [active | Rest], Acc) ->
%% Regardless of the option reuse_sessions we need the session_id_tracker
%% to generate session ids, but no sessions will be stored unless
%% reuse_sessions = true.
-session_id_tracker(_) ->
- dtls_server_session_cache_sup:start_child(ssl_server_session_cache_sup:session_opts()).
+session_id_tracker(Listner,_) ->
+ dtls_server_session_cache_sup:start_child(Listner).
+
+get_internal_active_n() ->
+ case application:get_env(ssl, internal_active_n) of
+ {ok, N} when is_integer(N) ->
+ N;
+ _ ->
+ ?INTERNAL_ACTIVE_N
+ end.
+
diff --git a/lib/ssl/src/dtls_server_session_cache_sup.erl b/lib/ssl/src/dtls_server_session_cache_sup.erl
index 42cbda27ec..457eb90167 100644
--- a/lib/ssl/src/dtls_server_session_cache_sup.erl
+++ b/lib/ssl/src/dtls_server_session_cache_sup.erl
@@ -41,8 +41,8 @@
%%%=========================================================================
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
-start_child(Args) ->
- supervisor:start_child(?MODULE, [self() | Args]).
+start_child(Listener) ->
+ supervisor:start_child(?MODULE, [Listener | ssl_config:pre_1_3_session_opts()]).
%%%=========================================================================
%%% Supervisor callback
diff --git a/lib/ssl/src/dtls_socket.erl b/lib/ssl/src/dtls_socket.erl
index 5fe4311af1..f1569f5069 100644
--- a/lib/ssl/src/dtls_socket.erl
+++ b/lib/ssl/src/dtls_socket.erl
@@ -46,33 +46,26 @@
send(Transport, {{IP,Port},Socket}, Data) ->
Transport:send(Socket, IP, Port, Data).
-listen(Port, #config{transport_info = TransportInfo,
- ssl = SslOpts,
- emulated = EmOpts0,
- inet_user = Options} = Config) ->
-
- Result = case dtls_listener_sup:lookup_listner(Port) of
- undefined ->
- Result0 = {ok, Listner0} =
- dtls_listener_sup:start_child([Port, TransportInfo, emulated_socket_options(EmOpts0, #socket_options{}),
- Options ++ internal_inet_values(), SslOpts]),
- dtls_listener_sup:register_listner({self(), Listner0}, Port),
- Result0;
- {ok, Listner0} = Result0 ->
- dtls_packet_demux:new_owner(Listner0),
- dtls_packet_demux:set_all_opts(Listner0, {Options, emulated_socket_options(EmOpts0, #socket_options{}), SslOpts}),
- dtls_listener_sup:register_listner({self(), Listner0}, Port),
- Result0;
- Result0 ->
- Result0
- end,
- case Result of
- {ok, Listner} ->
- Socket = #sslsocket{pid = {dtls, Config#config{dtls_handler = {Listner, Port}}}},
- check_active_n(EmOpts0, Socket),
- {ok, Socket};
- Err ->
- Err
+listen(Port, #config{inet_ssl = SockOpts,
+ ssl = SslOpts,
+ emulated = EmOpts,
+ inet_user = Options} = Config) ->
+ IP = proplists:get_value(ip, SockOpts, {0,0,0,0}),
+ case dtls_listener_sup:lookup_listener(IP, Port) of
+ undefined ->
+ start_new_listener(IP, Port, Config);
+ {ok, Listener} ->
+ dtls_packet_demux:new_owner(Listener),
+ dtls_packet_demux:set_all_opts(
+ Listener, {Options,
+ emulated_socket_options(EmOpts,
+ #socket_options{}),
+ SslOpts}),
+ dtls_listener_sup:register_listener({self(), Listener},
+ IP, Port),
+ {ok, create_dtls_socket(Config, Listener, Port)};
+ Error ->
+ Error
end.
accept(dtls, #config{transport_info = {Transport,_,_,_,_},
@@ -92,7 +85,7 @@ connect(Address, Port, #config{transport_info = {Transport, _, _, _, _} = CbInfo
inet_ssl = SocketOpts}, Timeout) ->
case Transport:open(0, SocketOpts ++ internal_inet_values()) of
{ok, Socket} ->
- ssl_connection:connect(ConnectionCb, Address, Port, {{Address, Port},Socket},
+ ssl_gen_statem:connect(ConnectionCb, Address, Port, {{Address, Port},Socket},
{SslOpts,
emulated_socket_options(EmOpts, #socket_options{}), undefined},
self(), CbInfo, Timeout);
@@ -100,8 +93,11 @@ connect(Address, Port, #config{transport_info = {Transport, _, _, _, _} = CbInfo
Error
end.
-close(#sslsocket{pid = {dtls, #config{dtls_handler = {Pid, Port}}}}) ->
- dtls_listener_sup:register_listner({undefined, Pid}, Port),
+close(#sslsocket{pid = {dtls, #config{dtls_handler = {Pid, Port0},
+ inet_ssl = SockOpts}}}) ->
+ IP = proplists:get_value(ip, SockOpts, {0,0,0,0}),
+ Port = get_real_port(Pid, Port0),
+ dtls_listener_sup:register_listener({undefined, Pid}, IP, Port),
dtls_packet_demux:close(Pid).
close(_, dtls) ->
@@ -111,10 +107,11 @@ close(gen_udp, {_Client, _Socket}) ->
close(Transport, {_Client, Socket}) ->
Transport:close(Socket).
-socket(Pids, gen_udp = Transport, {{_, _}, Socket}, ConnectionCb) ->
+socket(Pids, gen_udp = Transport,
+ PeerAndSock = {{_Host, _Port}, _Socket}, ConnectionCb) ->
#sslsocket{pid = Pids,
%% "The name "fd" is keept for backwards compatibility
- fd = {Transport, Socket, ConnectionCb}};
+ fd = {Transport, PeerAndSock, ConnectionCb}};
socket(Pids, Transport, Socket, ConnectionCb) ->
#sslsocket{pid = Pids,
%% "The name "fd" is keept for backwards compatibility
@@ -179,14 +176,19 @@ getstat(gen_udp, Pid, Options) when is_pid(Pid) ->
dtls_packet_demux:getstat(Pid, Options);
getstat(gen_udp, {_,{_, Socket}}, Options) ->
inet:getstat(Socket, Options);
+getstat(gen_udp, {_, Socket}, Options) ->
+ inet:getstat(Socket, Options);
getstat(gen_udp, Socket, Options) ->
inet:getstat(Socket, Options);
getstat(Transport, Socket, Options) ->
Transport:getstat(Socket, Options).
+
peername(_, undefined) ->
{error, enotconn};
peername(gen_udp, {_, {Client, _Socket}}) ->
{ok, Client};
+peername(gen_udp, {Client, _Socket}) ->
+ {ok, Client};
peername(Transport, Socket) ->
Transport:peername(Socket).
sockname(gen_udp, {_, {_,Socket}}) ->
@@ -270,3 +272,59 @@ validate_inet_option(active, Value)
throw({error, {options, {active,Value}}});
validate_inet_option(_, _) ->
ok.
+
+get_real_port(Listener, Port0) when is_pid(Listener) andalso
+ is_integer(Port0) ->
+ case Port0 of
+ 0 ->
+ {ok, {_, NewPort}} = dtls_packet_demux:sockname(Listener),
+ NewPort;
+ _ ->
+ Port0
+ end.
+
+start_new_listener(IP, Port0,
+ #config{transport_info = {TransportModule, _,_,_,_},
+ inet_user = Options} = Config) ->
+ InetOptions = Options ++ internal_inet_values(),
+ case TransportModule:open(Port0, InetOptions) of
+ {ok, Socket} ->
+ Port = case Port0 of
+ 0 ->
+ {ok, P} = inet:port(Socket),
+ P;
+ _ ->
+ Port0
+ end,
+ start_dtls_packet_demux(Config, IP, Port, Socket);
+ {error, eaddrinuse} ->
+ {error, already_listening};
+ Error ->
+ Error
+ end.
+
+start_dtls_packet_demux(#config{
+ transport_info =
+ {TransportModule, _,_,_,_} = TransportInfo,
+ emulated = EmOpts0,
+ ssl = SslOpts} = Config, IP, Port, Socket) ->
+ EmOpts = emulated_socket_options(EmOpts0, #socket_options{}),
+ case dtls_listener_sup:start_child([Port, TransportInfo, EmOpts,
+ SslOpts, Socket]) of
+ {ok, Multiplexer} ->
+ ok = TransportModule:controlling_process(Socket, Multiplexer),
+ dtls_listener_sup:register_listener({self(), Multiplexer},
+ IP, Port),
+ DTLSSocket = create_dtls_socket(Config, Multiplexer, Port),
+ {ok, DTLSSocket};
+ Error ->
+ Error
+ end.
+
+create_dtls_socket(#config{emulated = EmOpts} = Config,
+ Listener, Port) ->
+ Socket = #sslsocket{
+ pid = {dtls, Config#config{dtls_handler = {Listener, Port}}}},
+ check_active_n(EmOpts, Socket),
+ Socket.
+
diff --git a/lib/ssl/src/inet6_tls_dist.erl b/lib/ssl/src/inet6_tls_dist.erl
index 9ce13ca281..5ca0cd6904 100644
--- a/lib/ssl/src/inet6_tls_dist.erl
+++ b/lib/ssl/src/inet6_tls_dist.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2015-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2015-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -23,7 +23,7 @@
-export([childspecs/0]).
-export([listen/2, accept/1, accept_connection/5,
- setup/5, close/1, select/1]).
+ setup/5, close/1, select/1, address/0]).
childspecs() ->
inet_tls_dist:childspecs().
@@ -31,6 +31,9 @@ childspecs() ->
select(Node) ->
inet_tls_dist:gen_select(inet6_tcp, Node).
+address() ->
+ inet_tls_dist:gen_address(inet6_tcp).
+
listen(Name, Host) ->
inet_tls_dist:gen_listen(inet6_tcp, Name, Host).
diff --git a/lib/ssl/src/inet_tls_dist.erl b/lib/ssl/src/inet_tls_dist.erl
index 9bf8e40b70..eaa481f119 100644
--- a/lib/ssl/src/inet_tls_dist.erl
+++ b/lib/ssl/src/inet_tls_dist.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2019. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -23,11 +23,11 @@
-export([childspecs/0]).
-export([listen/2, accept/1, accept_connection/5,
- setup/5, close/1, select/1, is_node_name/1]).
+ setup/5, close/1, select/1, address/0, is_node_name/1]).
%% Generalized dist API
-export([gen_listen/3, gen_accept/2, gen_accept_connection/6,
- gen_setup/6, gen_close/2, gen_select/2]).
+ gen_setup/6, gen_close/2, gen_select/2, gen_address/1]).
-export([nodelay/0]).
@@ -63,6 +63,14 @@ gen_select(Driver, Node) ->
false
end.
+%% ------------------------------------------------------------
+%% Get the address family that this distribution uses
+%% ------------------------------------------------------------
+address() ->
+ gen_address(inet_tcp).
+gen_address(Driver) ->
+ inet_tcp_dist:gen_address(Driver).
+
%% -------------------------------------------------------------------------
is_node_name(Node) ->
@@ -758,8 +766,8 @@ nodelay() ->
get_ssl_options(Type) ->
try ets:lookup(ssl_dist_opts, Type) of
- [{Type, Opts}] ->
- [{erl_dist, true}, {versions, ['tlsv1.2']} | Opts];
+ [{Type, Opts0}] ->
+ [{erl_dist, true} | dist_defaults(Opts0)];
_ ->
get_ssl_dist_arguments(Type)
catch
@@ -770,11 +778,18 @@ get_ssl_options(Type) ->
get_ssl_dist_arguments(Type) ->
case init:get_argument(ssl_dist_opt) of
{ok, Args} ->
- [{erl_dist, true}, {versions, ['tlsv1.2']} | ssl_options(Type, lists:append(Args))];
+ [{erl_dist, true} | dist_defaults(ssl_options(Type, lists:append(Args)))];
_ ->
- [{erl_dist, true}, {versions, ['tlsv1.2']}]
+ [{erl_dist, true}]
end.
+dist_defaults(Opts) ->
+ case proplists:get_value(versions, Opts, undefined) of
+ undefined ->
+ [{versions, ['tlsv1.2']} | Opts];
+ _ ->
+ Opts
+ end.
ssl_options(_Type, []) ->
[];
diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src
index c524aafe71..f3cc463cff 100644
--- a/lib/ssl/src/ssl.app.src
+++ b/lib/ssl/src/ssl.app.src
@@ -12,6 +12,7 @@
tls_socket,
tls_v1,
tls_connection_sup,
+ tls_gen_connection,
tls_sender,
tls_server_sup,
tls_server_session_ticket_sup,
@@ -25,6 +26,7 @@
dtls_socket,
dtls_v1,
dtls_connection_sup,
+ dtls_gen_connection,
dtls_packet_demux,
dtls_listener_sup,
dtls_sup,
@@ -34,8 +36,9 @@
ssl, %% Main API
ssl_session_cache_api,
%% Both TLS/SSL and DTLS
+ tls_dtls_connection,
ssl_config,
- ssl_connection,
+ ssl_gen_statem,
ssl_handshake,
ssl_record,
ssl_cipher,
@@ -51,12 +54,15 @@
ssl_dist_sup,
ssl_dist_connection_sup,
ssl_dist_admin_sup,
+ tls_dist_sup,
+ tls_dist_server_sup,
%% SSL/TLS session and cert handling
ssl_session,
ssl_session_cache,
ssl_server_session_cache,
ssl_server_session_cache_db,
ssl_server_session_cache_sup,
+ ssl_upgrade_server_session_cache_sup,
ssl_manager,
ssl_pem_cache,
ssl_pkix_db,
@@ -78,5 +84,5 @@
{applications, [crypto, public_key, kernel, stdlib]},
{env, []},
{mod, {ssl_app, []}},
- {runtime_dependencies, ["stdlib-3.5","public_key-1.8","kernel-6.0",
+ {runtime_dependencies, ["stdlib-3.12","public_key-1.8","kernel-6.0",
"erts-10.0","crypto-4.2", "inets-5.10.7"]}]}.
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index c5471a3070..1b1b5d50f1 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -20,7 +20,8 @@
%%
-%%% Purpose : Main API module for SSL see also tls.erl and dtls.erl
+%%% Purpose : Main API module for the SSL application that implements TLS and DTLS
+%%% SSL is a legacy name.
-module(ssl).
@@ -300,7 +301,7 @@
%% -------------------------------------------------------------------------------------------------------
-type common_option() :: {protocol, protocol()} |
{handshake, handshake_completion()} |
- {cert, cert()} |
+ {cert, cert() | [cert()]} |
{certfile, cert_pem()} |
{key, key()} |
{keyfile, key_pem()} |
@@ -310,6 +311,7 @@
{signature_algs_cert, signature_schemes()} |
{supported_groups, supported_groups()} |
{secure_renegotiate, secure_renegotiation()} |
+ {keep_secrets, keep_secrets()} |
{depth, allowed_cert_chain_length()} |
{verify_fun, custom_verify()} |
{crl_check, crl_check()} |
@@ -346,6 +348,7 @@
-type cipher_filters() :: list({key_exchange | cipher | mac | prf,
algo_filter()}). % exported
-type algo_filter() :: fun((kex_algo()|cipher()|hash()|aead|default_prf) -> true | false).
+-type keep_secrets() :: boolean().
-type secure_renegotiation() :: boolean().
-type allowed_cert_chain_length() :: integer().
@@ -405,7 +408,7 @@
%% {ocsp_nonce, ocsp_nonce()}.
-type client_verify_type() :: verify_type().
--type client_reuse_session() :: session_id().
+-type client_reuse_session() :: session_id() | {session_id(), SessionData::binary()}.
-type client_reuse_sessions() :: boolean() | save.
-type client_cacerts() :: [public_key:der_encoded()].
-type client_cafile() :: file:filename().
@@ -505,6 +508,7 @@
client_random |
server_random |
master_secret |
+ keylog |
tls_options_name().
-type tls_options_name() :: atom().
%% -------------------------------------------------------------------------------------------------------
@@ -550,7 +554,7 @@ stop() ->
TCPSocket :: socket(),
TLSOptions :: [tls_client_option()].
-connect(Socket, SslOptions) when is_port(Socket) ->
+connect(Socket, SslOptions) ->
connect(Socket, SslOptions, infinity).
-spec connect(TCPSocket, TLSOptions, Timeout) ->
@@ -567,24 +571,23 @@ connect(Socket, SslOptions) when is_port(Socket) ->
Port :: inet:port_number(),
TLSOptions :: [tls_client_option()].
-connect(Socket, SslOptions0, Timeout) when is_port(Socket),
+connect(Socket, SslOptions0, Timeout) when is_list(SslOptions0) andalso
(is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
+
CbInfo = handle_option_cb_info(SslOptions0, tls),
-
Transport = element(1, CbInfo),
EmulatedOptions = tls_socket:emulated_options(),
{ok, SocketValues} = tls_socket:getopts(Transport, Socket, EmulatedOptions),
try handle_options(SslOptions0 ++ SocketValues, client) of
- {ok, Config} ->
- tls_socket:upgrade(Socket, Config, Timeout)
+ {ok, Config} ->
+ tls_socket:upgrade(Socket, Config, Timeout)
catch
- _:{error, Reason} ->
+ _:{error, Reason} ->
{error, Reason}
- end;
+ end;
connect(Host, Port, Options) ->
connect(Host, Port, Options, infinity).
-
-spec connect(Host, Port, TLSOptions, Timeout) ->
{ok, sslsocket()} |
{ok, sslsocket(),Ext :: protocol_extensions()} |
@@ -599,9 +602,9 @@ connect(Host, Port, Options, Timeout) when (is_integer(Timeout) andalso Timeout
try
{ok, Config} = handle_options(Options, client, Host),
case Config#config.connection_cb of
- tls_connection ->
+ tls_gen_connection ->
tls_socket:connect(Host,Port,Config,Timeout);
- dtls_connection ->
+ dtls_gen_connection ->
dtls_socket:connect(Host,Port,Config,Timeout)
end
catch
@@ -650,9 +653,9 @@ transport_accept(#sslsocket{pid = {ListenSocket,
#config{connection_cb = ConnectionCb} = Config}}, Timeout)
when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
case ConnectionCb of
- tls_connection ->
+ tls_gen_connection ->
tls_socket:accept(ListenSocket, Config, Timeout);
- dtls_connection ->
+ dtls_gen_connection ->
dtls_socket:accept(ListenSocket, Config, Timeout)
end.
@@ -731,7 +734,7 @@ handshake(ListenSocket) ->
handshake(#sslsocket{} = Socket, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or
(Timeout == infinity) ->
- ssl_connection:handshake(Socket, Timeout);
+ ssl_gen_statem:handshake(Socket, Timeout);
%% If Socket is a ordinary socket(): upgrades a gen_tcp, or equivalent, socket to
%% an SSL socket, that is, performs the SSL/TLS server-side handshake and returns
@@ -739,9 +742,8 @@ handshake(#sslsocket{} = Socket, Timeout) when (is_integer(Timeout) andalso Tim
%%
%% If Socket is an sslsocket(): provides extra SSL/TLS/DTLS options to those
%% specified in ssl:listen/2 and then performs the SSL/TLS/DTLS handshake.
-handshake(ListenSocket, SslOptions) when is_port(ListenSocket) ->
+handshake(ListenSocket, SslOptions) ->
handshake(ListenSocket, SslOptions, infinity).
-
-spec handshake(Socket, Options, Timeout) ->
{ok, SslSocket} |
{ok, SslSocket, Ext} |
@@ -761,7 +763,7 @@ handshake(#sslsocket{fd = {_, _, _, Trackers}} = Socket, SslOpts, Timeout) when
try
Tracker = proplists:get_value(option_tracker, Trackers),
{ok, EmOpts, _} = tls_socket:get_all_opts(Tracker),
- ssl_connection:handshake(Socket, {SslOpts,
+ ssl_gen_statem:handshake(Socket, {SslOpts,
tls_socket:emulated_socket_options(EmOpts, #socket_options{})}, Timeout)
catch
Error = {error, _Reason} -> Error
@@ -770,33 +772,30 @@ handshake(#sslsocket{pid = [Pid|_], fd = {_, _, _}} = Socket, SslOpts, Timeout)
(is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)->
try
{ok, EmOpts, _} = dtls_packet_demux:get_all_opts(Pid),
- ssl_connection:handshake(Socket, {SslOpts,
+ ssl_gen_statem:handshake(Socket, {SslOpts,
tls_socket:emulated_socket_options(EmOpts, #socket_options{})}, Timeout)
catch
Error = {error, _Reason} -> Error
end;
-handshake(Socket, SslOptions, Timeout) when is_port(Socket),
- (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
+handshake(Socket, SslOptions, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
CbInfo = handle_option_cb_info(SslOptions, tls),
-
Transport = element(1, CbInfo),
EmulatedOptions = tls_socket:emulated_options(),
{ok, SocketValues} = tls_socket:getopts(Transport, Socket, EmulatedOptions),
ConnetionCb = connection_cb(SslOptions),
try handle_options(SslOptions ++ SocketValues, server) of
- {ok, #config{transport_info = CbInfo, ssl = SslOpts, emulated = EmOpts}} ->
- ok = tls_socket:setopts(Transport, Socket, tls_socket:internal_inet_values()),
- {ok, Port} = tls_socket:port(Transport, Socket),
- {ok, SessionIdHandle} = tls_socket:session_id_tracker(SslOpts),
- ssl_connection:handshake(ConnetionCb, Port, Socket,
+ {ok, #config{transport_info = CbInfo, ssl = SslOpts, emulated = EmOpts}} ->
+ ok = tls_socket:setopts(Transport, Socket, tls_socket:internal_inet_values()),
+ {ok, Port} = tls_socket:port(Transport, Socket),
+ {ok, SessionIdHandle} = tls_socket:session_id_tracker(ssl_unknown_listener, SslOpts),
+ ssl_gen_statem:handshake(ConnetionCb, Port, Socket,
{SslOpts,
tls_socket:emulated_socket_options(EmOpts, #socket_options{}),
[{session_id_tracker, SessionIdHandle}]},
self(), CbInfo, Timeout)
catch
- Error = {error, _Reason} -> Error
- end.
-
+ Error = {error, _Reason} -> Error
+ end.
%%--------------------------------------------------------------------
-spec handshake_continue(HsSocket, Options) ->
@@ -824,14 +823,14 @@ handshake_continue(Socket, SSLOptions) ->
%% Description: Continues the handshke possible with newly supplied options.
%%--------------------------------------------------------------------
handshake_continue(Socket, SSLOptions, Timeout) ->
- ssl_connection:handshake_continue(Socket, SSLOptions, Timeout).
+ ssl_gen_statem:handshake_continue(Socket, SSLOptions, Timeout).
%%--------------------------------------------------------------------
-spec handshake_cancel(#sslsocket{}) -> any().
%%
%% Description: Cancels the handshakes sending a close alert.
%%--------------------------------------------------------------------
handshake_cancel(Socket) ->
- ssl_connection:handshake_cancel(Socket).
+ ssl_gen_statem:handshake_cancel(Socket).
%%--------------------------------------------------------------------
-spec close(SslSocket) -> ok | {error, Reason} when
@@ -841,7 +840,7 @@ handshake_cancel(Socket) ->
%% Description: Close an ssl connection
%%--------------------------------------------------------------------
close(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) ->
- ssl_connection:close(Pid, {close, ?DEFAULT_TIMEOUT});
+ ssl_gen_statem:close(Pid, {close, ?DEFAULT_TIMEOUT});
close(#sslsocket{pid = {dtls, #config{dtls_handler = {_, _}}}} = DTLSListen) ->
dtls_socket:close(DTLSListen);
close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_,_,_,_}}}}) ->
@@ -859,7 +858,7 @@ close(#sslsocket{pid = [TLSPid|_]},
{Pid, Timeout} = DownGrade) when is_pid(TLSPid),
is_pid(Pid),
(is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
- case ssl_connection:close(TLSPid, {close, DownGrade}) of
+ case ssl_gen_statem:close(TLSPid, {close, DownGrade}) of
ok -> %% In normal close {error, closed} is regarded as ok, as it is not interesting which side
%% that got to do the actual close. But in the downgrade case only {ok, Port} is a sucess.
{error, closed};
@@ -868,7 +867,7 @@ close(#sslsocket{pid = [TLSPid|_]},
end;
close(#sslsocket{pid = [TLSPid|_]}, Timeout) when is_pid(TLSPid),
(is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
- ssl_connection:close(TLSPid, {close, Timeout});
+ ssl_gen_statem:close(TLSPid, {close, Timeout});
close(#sslsocket{pid = {dtls = ListenSocket, #config{transport_info={Transport,_,_,_,_}}}}, _) ->
dtls_socket:close(Transport, ListenSocket);
close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_,_,_,_}}}}, _) ->
@@ -882,7 +881,7 @@ close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_,_,_,_}
%% Description: Sends data over the ssl connection
%%--------------------------------------------------------------------
send(#sslsocket{pid = [Pid]}, Data) when is_pid(Pid) ->
- ssl_connection:send(Pid, Data);
+ ssl_gen_statem:send(Pid, Data);
send(#sslsocket{pid = [_, Pid]}, Data) when is_pid(Pid) ->
tls_sender:send_data(Pid, erlang:iolist_to_iovec(Data));
send(#sslsocket{pid = {_, #config{transport_info={_, udp, _, _}}}}, _) ->
@@ -915,7 +914,7 @@ recv(Socket, Length) ->
recv(#sslsocket{pid = [Pid|_]}, Length, Timeout) when is_pid(Pid),
(is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)->
- ssl_connection:recv(Pid, Length, Timeout);
+ ssl_gen_statem:recv(Pid, Length, Timeout);
recv(#sslsocket{pid = {dtls,_}}, _, _) ->
{error,enotconn};
recv(#sslsocket{pid = {Listen,
@@ -933,7 +932,7 @@ recv(#sslsocket{pid = {Listen,
%% or once.
%%--------------------------------------------------------------------
controlling_process(#sslsocket{pid = [Pid|_]}, NewOwner) when is_pid(Pid), is_pid(NewOwner) ->
- ssl_connection:new_user(Pid, NewOwner);
+ ssl_gen_statem:new_user(Pid, NewOwner);
controlling_process(#sslsocket{pid = {dtls, _}},
NewOwner) when is_pid(NewOwner) ->
ok; %% Meaningless but let it be allowed to conform with TLS
@@ -953,7 +952,7 @@ controlling_process(#sslsocket{pid = {Listen,
%% Description: Return SSL information for the connection
%%--------------------------------------------------------------------
connection_information(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) ->
- case ssl_connection:connection_information(Pid, false) of
+ case ssl_gen_statem:connection_information(Pid, false) of
{ok, Info} ->
{ok, [Item || Item = {_Key, Value} <- Info, Value =/= undefined]};
Error ->
@@ -973,7 +972,7 @@ connection_information(#sslsocket{pid = {dtls,_}}) ->
%% Description: Return SSL information for the connection
%%--------------------------------------------------------------------
connection_information(#sslsocket{pid = [Pid|_]}, Items) when is_pid(Pid) ->
- case ssl_connection:connection_information(Pid, include_security_info(Items)) of
+ case ssl_gen_statem:connection_information(Pid, include_security_info(Items)) of
{ok, Info} ->
{ok, [Item || Item = {Key, Value} <- Info, lists:member(Key, Items),
Value =/= undefined]};
@@ -1009,7 +1008,7 @@ peername(#sslsocket{pid = {dtls,_}}) ->
%% Description: Returns the peercert.
%%--------------------------------------------------------------------
peercert(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) ->
- case ssl_connection:peer_certificate(Pid) of
+ case ssl_gen_statem:peer_certificate(Pid) of
{ok, undefined} ->
{error, no_peercert};
Result ->
@@ -1030,7 +1029,7 @@ peercert(#sslsocket{pid = {Listen, _}}) when is_port(Listen) ->
%% protocol has been negotiated will return {error, protocol_not_negotiated}
%%--------------------------------------------------------------------
negotiated_protocol(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) ->
- ssl_connection:negotiated_protocol(Pid).
+ ssl_gen_statem:negotiated_protocol(Pid).
%%--------------------------------------------------------------------
-spec cipher_suites() -> [old_cipher_suite()] | [string()].
@@ -1207,7 +1206,7 @@ groups(default) ->
%% Description: Gets options
%%--------------------------------------------------------------------
getopts(#sslsocket{pid = [Pid|_]}, OptionTags) when is_pid(Pid), is_list(OptionTags) ->
- ssl_connection:get_opts(Pid, OptionTags);
+ ssl_gen_statem:get_opts(Pid, OptionTags);
getopts(#sslsocket{pid = {dtls, #config{transport_info = {Transport,_,_,_,_}}}} = ListenSocket, OptionTags) when is_list(OptionTags) ->
try dtls_socket:getopts(Transport, ListenSocket, OptionTags) of
{ok, _} = Result ->
@@ -1245,11 +1244,11 @@ setopts(#sslsocket{pid = [Pid, Sender]}, Options0) when is_pid(Pid), is_list(Opt
Options ->
case proplists:get_value(packet, Options, undefined) of
undefined ->
- ssl_connection:set_opts(Pid, Options);
+ ssl_gen_statem:set_opts(Pid, Options);
PacketOpt ->
case tls_sender:setopts(Sender, [{packet, PacketOpt}]) of
ok ->
- ssl_connection:set_opts(Pid, Options);
+ ssl_gen_statem:set_opts(Pid, Options);
Error ->
Error
end
@@ -1262,7 +1261,7 @@ setopts(#sslsocket{pid = [Pid|_]}, Options0) when is_pid(Pid), is_list(Options0)
try proplists:expand([{binary, [{mode, binary}]},
{list, [{mode, list}]}], Options0) of
Options ->
- ssl_connection:set_opts(Pid, Options)
+ ssl_gen_statem:set_opts(Pid, Options)
catch
_:_ ->
{error, {options, {not_a_proplist, Options0}}}
@@ -1338,7 +1337,7 @@ shutdown(#sslsocket{pid = {Listen, #config{transport_info = Info}}},
shutdown(#sslsocket{pid = {dtls,_}},_) ->
{error, enotconn};
shutdown(#sslsocket{pid = [Pid|_]}, How) when is_pid(Pid) ->
- ssl_connection:shutdown(Pid, How).
+ ssl_gen_statem:shutdown(Pid, How).
%%--------------------------------------------------------------------
-spec sockname(SslSocket) ->
@@ -1403,12 +1402,12 @@ renegotiate(#sslsocket{pid = [Pid, Sender |_]}) when is_pid(Pid),
is_pid(Sender) ->
case tls_sender:renegotiate(Sender) of
{ok, Write} ->
- tls_connection:renegotiation(Pid, Write);
+ tls_dtls_connection:renegotiation(Pid, Write);
Error ->
Error
end;
renegotiate(#sslsocket{pid = [Pid |_]}) when is_pid(Pid) ->
- ssl_connection:renegotiation(Pid);
+ tls_dtls_connection:renegotiation(Pid);
renegotiate(#sslsocket{pid = {dtls,_}}) ->
{error, enotconn};
renegotiate(#sslsocket{pid = {Listen,_}}) when is_port(Listen) ->
@@ -1432,7 +1431,7 @@ update_keys(#sslsocket{pid = [Pid, Sender |_]}, Type0) when is_pid(Pid) andalso
read_write ->
update_requested
end,
- tls_connection:send_key_update(Sender, Type);
+ tls_connection_1_3:send_key_update(Sender, Type);
update_keys(_, Type) ->
{error, {illegal_parameter, Type}}.
@@ -1449,7 +1448,7 @@ update_keys(_, Type) ->
%%--------------------------------------------------------------------
prf(#sslsocket{pid = [Pid|_]},
Secret, Label, Seed, WantedLength) when is_pid(Pid) ->
- ssl_connection:prf(Pid, Secret, Label, Seed, WantedLength);
+ tls_dtls_connection:prf(Pid, Secret, Label, Seed, WantedLength);
prf(#sslsocket{pid = {dtls,_}}, _,_,_,_) ->
{error, enotconn};
prf(#sslsocket{pid = {Listen,_}}, _,_,_,_) when is_port(Listen) ->
@@ -1568,10 +1567,10 @@ supported_suites(all, Version) ->
supported_suites(anonymous, Version) ->
ssl_cipher:anonymous_suites(Version).
-do_listen(Port, #config{transport_info = {Transport, _, _, _,_}} = Config, tls_connection) ->
+do_listen(Port, #config{transport_info = {Transport, _, _, _,_}} = Config, tls_gen_connection) ->
tls_socket:listen(Transport, Port, Config);
-do_listen(Port, Config, dtls_connection) ->
+do_listen(Port, Config, dtls_gen_connection) ->
dtls_socket:listen(Port, Config).
@@ -2128,8 +2127,11 @@ validate_option(depth, Value) when is_integer(Value),
Value >= 0, Value =< 255->
Value;
validate_option(cert, Value) when Value == undefined;
- is_binary(Value) ->
+ is_list(Value)->
Value;
+validate_option(cert, Value) when Value == undefined;
+ is_binary(Value)->
+ [Value];
validate_option(certfile, undefined = Value) ->
Value;
validate_option(certfile, Value) when is_binary(Value) ->
@@ -2203,12 +2205,17 @@ validate_option(reuse_session, Value) when is_function(Value) ->
Value;
validate_option(reuse_session, Value) when is_binary(Value) ->
Value;
+validate_option(reuse_session, {Id, Data} = Value) when is_binary(Id) andalso
+ is_binary(Data) ->
+ Value;
validate_option(reuse_sessions, Value) when is_boolean(Value) ->
Value;
validate_option(reuse_sessions, save = Value) ->
Value;
validate_option(secure_renegotiate, Value) when is_boolean(Value) ->
Value;
+validate_option(keep_secrets, Value) when is_boolean(Value) ->
+ Value;
validate_option(client_renegotiation, Value) when is_boolean(Value) ->
Value;
validate_option(renegotiate_at, Value) when is_integer(Value) ->
@@ -2684,9 +2691,9 @@ make_next_protocol_selector({server, AllProtocols, DefaultProtocol}) ->
end.
connection_cb(tls) ->
- tls_connection;
+ tls_gen_connection;
connection_cb(dtls) ->
- dtls_connection;
+ dtls_gen_connection;
connection_cb(Opts) ->
connection_cb(proplists:get_value(protocol, Opts, tls)).
@@ -2759,7 +2766,7 @@ default_cb_info(dtls) ->
include_security_info([]) ->
false;
include_security_info([Item | Items]) ->
- case lists:member(Item, [client_random, server_random, master_secret]) of
+ case lists:member(Item, [client_random, server_random, master_secret, keylog]) of
true ->
true;
false ->
diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl
index 0a9749de96..b5b0a23d85 100644
--- a/lib/ssl/src/ssl_certificate.erl
+++ b/lib/ssl/src/ssl_certificate.erl
@@ -31,7 +31,7 @@
-include("ssl_internal.hrl").
-include_lib("public_key/include/public_key.hrl").
--export([trusted_cert_and_path/4,
+-export([trusted_cert_and_paths/4,
certificate_chain/3,
certificate_chain/4,
file_to_certificats/2,
@@ -50,46 +50,47 @@
%%====================================================================
%%--------------------------------------------------------------------
--spec trusted_cert_and_path([der_cert()], db_handle(), certdb_ref(), fun()) ->
- {der_cert() | unknown_ca, [der_cert()]}.
+-spec trusted_cert_and_paths([der_cert()], db_handle(), certdb_ref(), fun()) ->
+ [{der_cert() | unknown_ca | invalid_issuer | selfsigned_peer, [der_cert()]}].
%%
-%% Description: Extracts the root cert (if not presents tries to
-%% look it up, if not found {bad_cert, unknown_ca} will be added verification
-%% errors. Returns {RootCert, Path, VerifyErrors}
+%% Description: Construct input to public_key:pkix_path_validation/3,
+%% If the ROOT cert is not found {bad_cert, unknown_ca} will be returned
+%% instead of the ROOT cert to be handled as a path validation error
+%% by the verify_fun.
+%% Returns {RootCert | RootCertRelatedError, Path}
+%% Note: Path = lists:reverse(Chain) -- Root, that is on the peer cert
+%% always comes first in the chain but last in the path.
%%--------------------------------------------------------------------
-trusted_cert_and_path(CertChain, CertDbHandle, CertDbRef, PartialChainHandler) ->
- Path = [BinCert | _] = lists:reverse(CertChain),
- OtpCert = public_key:pkix_decode_cert(BinCert, otp),
- SignedAndIssuerID =
- case public_key:pkix_is_self_signed(OtpCert) of
- true ->
- {ok, IssuerId} = public_key:pkix_issuer_id(OtpCert, self),
- {self, IssuerId};
- false ->
- other_issuer(OtpCert, BinCert, CertDbHandle, CertDbRef)
- end,
-
- case SignedAndIssuerID of
- {error, issuer_not_found} ->
- %% The root CA was not sent and cannot be found.
- handle_incomplete_chain(Path, PartialChainHandler);
- {self, _} when length(Path) == 1 ->
- {selfsigned_peer, Path};
- {_ ,{SerialNr, Issuer}} ->
- case ssl_manager:lookup_trusted_cert(CertDbHandle, CertDbRef, SerialNr, Issuer) of
- {ok, Trusted} ->
- %% Trusted must be selfsigned or it is an incomplete chain
- handle_path(Trusted, Path, PartialChainHandler);
- _ ->
- %% Root CA could not be verified, but partial
- %% chain handler may trusted a cert that we got
- handle_incomplete_chain(Path, PartialChainHandler)
- end
- end.
+trusted_cert_and_paths([Peer] = Chain, CertDbHandle, CertDbRef, PartialChainHandler) ->
+ OtpCert = public_key:pkix_decode_cert(Peer, otp),
+ case public_key:pkix_is_self_signed(OtpCert) of
+ true ->
+ [{selfsigned_peer, [Peer]}];
+ false ->
+ [handle_incomplete_chain(Chain, PartialChainHandler, {unknown_ca, [Peer]},
+ CertDbHandle, CertDbRef)]
+ end;
+trusted_cert_and_paths(Chain, CertDbHandle, CertDbRef, PartialChainHandler) ->
+ %% Construct possible certificate paths from the chain certificates.
+ %% If the chain contains extraneous certificates there could be
+ %% more than one possible path such chains might be used to phase out
+ %% an old certificate.
+ Paths = paths(Chain, CertDbHandle, CertDbRef),
+ lists:map(fun(Path) ->
+ case handle_partial_chain(Path, PartialChainHandler, CertDbHandle, CertDbRef) of
+ {unknown_ca, _} = Result ->
+ handle_incomplete_chain(Chain,
+ PartialChainHandler,
+ Result,
+ CertDbHandle, CertDbRef);
+ Result ->
+ Result
+ end
+ end, Paths).
%%--------------------------------------------------------------------
-spec certificate_chain(undefined | binary() | #'OTPCertificate'{} , db_handle(), certdb_ref()) ->
- {error, no_cert} | {ok, #'OTPCertificate'{} | undefined, [der_cert()]}.
+ {error, no_cert} | {ok, der_cert() | undefined, [der_cert()]}.
%%
%% Description: Return the certificate chain to send to peer.
%%--------------------------------------------------------------------
@@ -104,7 +105,7 @@ certificate_chain(OwnCert, CertDbHandle, CertsDbRef) ->
%%--------------------------------------------------------------------
-spec certificate_chain(undefined | binary() | #'OTPCertificate'{} , db_handle(), certdb_ref() | {extracted, list()}, [der_cert()]) ->
- {error, no_cert} | {ok, #'OTPCertificate'{} | undefined, [der_cert()]}.
+ {error, no_cert} | {ok, der_cert() | undefined, [der_cert()]}.
%%
%% Description: Create certificate chain with certs from
%%--------------------------------------------------------------------
@@ -359,31 +360,6 @@ other_issuer(OtpCert, BinCert, CertDbHandle, CertDbRef) ->
end
end.
-handle_path({BinCert, OTPCert}, Path, PartialChainHandler) ->
- case public_key:pkix_is_self_signed(OTPCert) of
- true ->
- {BinCert, lists:delete(BinCert, Path)};
- false ->
- handle_incomplete_chain(Path, PartialChainHandler)
- end.
-
-handle_incomplete_chain(Chain, Fun) ->
- case catch Fun(Chain) of
- {trusted_ca, DerCert} ->
- new_trusteded_chain(DerCert, Chain);
- unknown_ca = Error ->
- {Error, Chain};
- _ ->
- {unknown_ca, Chain}
- end.
-
-new_trusteded_chain(DerCert, [DerCert | Chain]) ->
- {DerCert, Chain};
-new_trusteded_chain(DerCert, [_ | Rest]) ->
- new_trusteded_chain(DerCert, Rest);
-new_trusteded_chain(_, []) ->
- {unknown_ca, []}.
-
verify_hostname({fallback, Hostname}, Customize, Cert, UserState) when is_list(Hostname) ->
case public_key:pkix_verify_hostname(Cert, [{dns_id, Hostname}], Customize) of
true ->
@@ -462,3 +438,161 @@ pre_1_3_hash(sha1) ->
sha;
pre_1_3_hash(Hash) ->
Hash.
+
+paths(Chain, CertDbHandle, CertDbRef) ->
+ paths(Chain, Chain, CertDbHandle, CertDbRef, []).
+
+paths([Root], _, _, _, Path) ->
+ [[Root | Path]];
+paths([Cert1, Cert2 | Rest], Chain, CertDbHandle, CertDbRef, Path) ->
+ case public_key:pkix_is_issuer(Cert1, Cert2) of
+ true ->
+ %% Chain orded so far
+ paths([Cert2 | Rest], Chain, CertDbHandle, CertDbRef, [Cert1 | Path]);
+ false ->
+ %% Chain is unorded and/or contains extraneous certificates
+ unorded_or_extraneous(Chain, CertDbHandle, CertDbRef)
+ end.
+
+unorded_or_extraneous([Peer | FalseChain], CertDbHandle, CertDbRef) ->
+ ChainCandidates = extraneous_chains(FalseChain),
+ lists:map(fun(Candidate) ->
+ path_candidate(Peer, Candidate, CertDbHandle, CertDbRef)
+ end,
+ ChainCandidates).
+
+path_candidate(Peer, ChainCandidateCAs, CertDbHandle, _CertDbRef) ->
+ {ok, ExtractedCerts} = ssl_pkix_db:extract_trusted_certs({der, ChainCandidateCAs}),
+ %% certificate_chain/4 will make sure the chain is ordered
+ case certificate_chain(Peer, CertDbHandle, ExtractedCerts, []) of
+ {ok, undefined, Chain} ->
+ lists:reverse(Chain);
+ {ok, Root, Chain} ->
+ [Root | lists:reverse(Chain)]
+ end.
+
+handle_partial_chain([IssuerCert| Rest] = Path, PartialChainHandler, CertDbHandle, CertDbRef) ->
+ case public_key:pkix_is_self_signed(IssuerCert) of
+ true -> %% IssuerCert = ROOT (That is ROOT was included in chain)
+ {ok, {SerialNr, IssuerId}} = public_key:pkix_issuer_id(IssuerCert, self),
+ case ssl_manager:lookup_trusted_cert(CertDbHandle, CertDbRef, SerialNr, IssuerId) of
+ {ok, {IssuerCert, _}} -> %% Match sent ROOT to trusted ROOT
+ maybe_shorten_path(Path, PartialChainHandler, {IssuerCert, Rest});
+ {ok, _} -> %% Did not match trusted ROOT
+ maybe_shorten_path(Path, PartialChainHandler, {invalid_issuer, Path});
+ _ ->
+ maybe_shorten_path(Path, PartialChainHandler, {unknown_ca, Path})
+ end;
+ false ->
+ OTPCert = public_key:pkix_decode_cert(IssuerCert, otp),
+ case other_issuer(OTPCert, IssuerCert, CertDbHandle, CertDbRef) of
+ {other, {SerialNr, IssuerId}} ->
+ case ssl_manager:lookup_trusted_cert(CertDbHandle, CertDbRef, SerialNr, IssuerId) of
+ {ok, {NewIssuerCert, _}} ->
+ case public_key:pkix_is_self_signed(NewIssuerCert) of
+ true -> %% NewIssuerCert is a trusted ROOT cert
+ maybe_shorten_path([NewIssuerCert | Path], PartialChainHandler, {NewIssuerCert, Path});
+ false ->
+ maybe_shorten_path([NewIssuerCert | Path], PartialChainHandler,
+ {unknown_ca, [NewIssuerCert | Path]})
+ end;
+ _ ->
+ maybe_shorten_path(Path, PartialChainHandler, {unknown_ca, Path})
+ end;
+ {error, issuer_not_found} ->
+ maybe_shorten_path(Path, PartialChainHandler, {unknown_ca, Path})
+ end
+ end.
+
+maybe_shorten_path(Path, PartialChainHandler, Default) ->
+ %% This function might shorthen the
+ %% certificate path to be validated with
+ %% public_key:pkix_path_validation by letting
+ %% the user put its trust in an intermidate cert
+ %% from the certifcate chain sent by the peer.
+ try PartialChainHandler(Path) of
+ {trusted_ca, Root} ->
+ new_trusteded_path(Root, Path, Default);
+ unknown_ca ->
+ Default
+ catch _:_ ->
+ Default
+ end.
+
+new_trusteded_path(DerCert, [DerCert | Chain], _) ->
+ {DerCert, Chain};
+new_trusteded_path(DerCert, [_ | Rest], Default) ->
+ new_trusteded_path(DerCert, Rest, Default);
+new_trusteded_path(_, [], Default) ->
+ %% User did not pick a cert present
+ %% in the cert chain so ignore
+ Default.
+
+handle_incomplete_chain([PeerCert| _] = Chain0, PartialChainHandler, Default, CertDbHandle, CertDbRef) ->
+ %% We received an incomplete chain, that is not all certs expected to be present are present.
+ %% See if we have the certificates to rebuild it.
+ case certificate_chain(PeerCert, CertDbHandle, CertDbRef) of
+ {ok, _, [PeerCert | _] = Chain} when Chain =/= Chain0 -> %% Chain candidate found
+ handle_partial_chain(lists:reverse(Chain), PartialChainHandler, CertDbHandle, CertDbRef);
+ _ ->
+ Default
+ end.
+
+extraneous_chains(Certs) ->
+ %% If some certs claim to be the same cert that is have the same
+ %% subject field we should create a list of possible chain certs
+ %% for each such cert. Only one chain, if any, should be
+ %% verifiable using available ROOT certs.
+ Subjects = [{subject(Cert), Cert} || Cert <- Certs],
+ Duplicates = find_duplicates(Subjects),
+ %% Number of certs with duplicates (same subject) has been limited
+ %% to two and the maximum number of combinations is limited to 4.
+ build_candidates(Duplicates, 2, 4).
+
+build_candidates(Map, Duplicates, Combinations) ->
+ Subjects = maps:keys(Map),
+ build_candidates(Subjects, Map, Duplicates, 1, Combinations, []).
+%%
+build_candidates([], _, _, _, _, Acc) ->
+ Acc;
+build_candidates([H|T], Map, Duplicates, Combinations, Max, Acc0) ->
+ case maps:get(H, Map) of
+ {Certs, Counter} when Counter > 1 andalso
+ Duplicates > 0 andalso
+ Counter * Combinations =< Max ->
+ case Acc0 of
+ [] ->
+ Acc = [[Cert] || Cert <- Certs],
+ build_candidates(T, Map, Duplicates - 1, Combinations * Counter, Max, Acc);
+ _Else ->
+ Acc = [[Cert|L] || Cert <- Certs, L <- Acc0],
+ build_candidates(T, Map, Duplicates - 1, Combinations * Counter, Max, Acc)
+ end;
+ {[Cert|_], _} ->
+ case Acc0 of
+ [] ->
+ Acc = [[Cert]],
+ build_candidates(T, Map, Duplicates, Combinations, Max, Acc);
+ _Else ->
+ Acc = [[Cert|L] || L <- Acc0],
+ build_candidates(T, Map, Duplicates, Combinations, Max, Acc)
+ end
+ end.
+
+find_duplicates(Chain) ->
+ find_duplicates(Chain, #{}).
+%%
+find_duplicates([], Acc) ->
+ Acc;
+find_duplicates([{Subject, Cert}|T], Acc) ->
+ case maps:get(Subject, Acc, none) of
+ none ->
+ find_duplicates(T, Acc#{Subject => {[Cert], 1}});
+ {Certs, Counter} ->
+ find_duplicates(T, Acc#{Subject => {[Cert|Certs], Counter + 1}})
+ end.
+
+subject(Cert) ->
+ {_Serial,Subject} = public_key:pkix_subject_id(Cert),
+ Subject.
+
diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl
index 6bc7f6e353..a7fac8722b 100644
--- a/lib/ssl/src/ssl_cipher.erl
+++ b/lib/ssl/src/ssl_cipher.erl
@@ -530,13 +530,15 @@ rsa_suites(0) ->
?TLS_RSA_WITH_AES_128_CBC_SHA,
?TLS_RSA_WITH_3DES_EDE_CBC_SHA
];
-rsa_suites(N) when N =< 4 ->
+rsa_suites(N) when N >= 3 ->
[
?TLS_RSA_WITH_AES_256_GCM_SHA384,
?TLS_RSA_WITH_AES_256_CBC_SHA256,
?TLS_RSA_WITH_AES_128_GCM_SHA256,
?TLS_RSA_WITH_AES_128_CBC_SHA256
- ].
+ ];
+rsa_suites(_) ->
+ [].
%%--------------------------------------------------------------------
-spec filter(undefined | binary(), [ssl_cipher_format:cipher_suite()],
diff --git a/lib/ssl/src/ssl_cipher.hrl b/lib/ssl/src/ssl_cipher.hrl
index 0a7c4560fb..9f2141b6f8 100644
--- a/lib/ssl/src/ssl_cipher.hrl
+++ b/lib/ssl/src/ssl_cipher.hrl
@@ -260,6 +260,18 @@
%% TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = { 0xC0, 0x0A }
-define(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, <<?BYTE(16#C0), ?BYTE(16#0A)>>).
+%% TLS_ECDHE_ECDSA_WITH_AES_128_CCM = {0xC0,0xAC}
+-define(TLS_ECDHE_ECDSA_WITH_AES_128_CCM, <<?BYTE(16#C0), ?BYTE(16#AC)>>).
+
+%% TLS_ECDHE_ECDSA_WITH_AES_256_CCM = {0xC0,0xAD}
+-define(TLS_ECDHE_ECDSA_WITH_AES_256_CCM, <<?BYTE(16#C0), ?BYTE(16#AD)>>).
+
+%% TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 = {0xC0,0xAE}
+-define(TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, <<?BYTE(16#C0), ?BYTE(16#AE)>>).
+
+%% TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8 = {0xC0,0xAF}
+-define(TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8, <<?BYTE(16#C0), ?BYTE(16#AF)>>).
+
%% ECDH_RSA
%% TLS_ECDH_RSA_WITH_NULL_SHA = { 0xC0, 0x0B }
diff --git a/lib/ssl/src/ssl_cipher_format.erl b/lib/ssl/src/ssl_cipher_format.erl
index e42f0b817b..589b0facf8 100644
--- a/lib/ssl/src/ssl_cipher_format.erl
+++ b/lib/ssl/src/ssl_cipher_format.erl
@@ -77,13 +77,13 @@ suite_map_to_str(#{key_exchange := Kex,
cipher := Cipher,
mac := aead,
prf := PRF}) ->
- "TLS_" ++ string:to_upper(atom_to_list(Kex)) ++
+ "TLS_" ++ kex_str(Kex) ++
"_WITH_" ++ string:to_upper(atom_to_list(Cipher)) ++
- "_" ++ string:to_upper(atom_to_list(PRF));
+ prf_str("_", PRF);
suite_map_to_str(#{key_exchange := Kex,
cipher := Cipher,
mac := Mac}) ->
- "TLS_" ++ string:to_upper(atom_to_list(Kex)) ++
+ "TLS_" ++ kex_str(Kex) ++
"_WITH_" ++ string:to_upper(atom_to_list(Cipher)) ++
"_" ++ string:to_upper(atom_to_list(Mac)).
@@ -97,12 +97,6 @@ suite_str_to_map(SuiteStr)->
case string:split(Str0, "_WITH_") of
[Rest] ->
tls_1_3_suite_str_to_map(Rest);
- [Prefix, Kex | Rest] when Prefix == "SPR";
- Prefix == "PSK";
- Prefix == "DHE";
- Prefix == "ECDHE"
- ->
- pre_tls_1_3_suite_str_to_map(Prefix ++ "_" ++ Kex, Rest);
[Kex| Rest] ->
pre_tls_1_3_suite_str_to_map(Kex, Rest)
end.
@@ -116,26 +110,36 @@ suite_map_to_openssl_str(#{key_exchange := null} = Suite) ->
suite_map_to_str(Suite);
suite_map_to_openssl_str(#{key_exchange := rsa = Kex,
cipher := Cipher,
- mac := Mac}) when Cipher == "des_cbc";
- Cipher == "3des_ede_cbc" ->
+ mac := aead,
+ prf := PRF}) when PRF =/= default_prf ->
+ openssl_cipher_name(Kex, string:to_upper(atom_to_list(Cipher))) ++
+ "-" ++ string:to_upper(atom_to_list(PRF));
+suite_map_to_openssl_str(#{key_exchange := Kex,
+ cipher := Cipher,
+ mac := Mac}) when (Kex == rsa) orelse
+ (Kex == srp_anon)
+ andalso
+ (Cipher == "des_cbc") orelse
+ (Cipher == "3des_ede_cbc") ->
openssl_cipher_name(Kex, string:to_upper(atom_to_list(Cipher))) ++
"-" ++ string:to_upper(atom_to_list(Mac));
suite_map_to_openssl_str(#{key_exchange := Kex,
cipher := chacha20_poly1305 = Cipher,
- mac := aead}) ->
- openssl_suite_start(string:to_upper(atom_to_list(Kex)))
- ++ openssl_cipher_name(Kex, string:to_upper(atom_to_list(Cipher)));
+ mac := aead,
+ prf := sha256}) ->
+ openssl_suite_start(kex_str(Kex), Cipher)
+ ++ openssl_cipher_name(Kex, string:to_upper(atom_to_list(Cipher)));
suite_map_to_openssl_str(#{key_exchange := Kex,
cipher := Cipher,
mac := aead,
prf := PRF}) ->
- openssl_suite_start(string:to_upper(atom_to_list(Kex)))
+ openssl_suite_start(kex_str(Kex), Cipher)
++ openssl_cipher_name(Kex, string:to_upper(atom_to_list(Cipher))) ++
- "-" ++ string:to_upper(atom_to_list(PRF));
+ prf_str("-", PRF);
suite_map_to_openssl_str(#{key_exchange := Kex,
- cipher := Cipher,
- mac := Mac}) ->
- openssl_suite_start(string:to_upper(atom_to_list(Kex)))
+ cipher := Cipher,
+ mac := Mac}) ->
+ openssl_suite_start(kex_str(Kex), Cipher)
++ openssl_cipher_name(Kex, string:to_upper(atom_to_list(Cipher))) ++
"-" ++ string:to_upper(atom_to_list(Mac)).
@@ -148,14 +152,20 @@ suite_openssl_str_to_map("DES-CBC3-SHA") ->
suite_str_to_map("TLS_RSA_WITH_3DES_EDE_CBC_SHA");
suite_openssl_str_to_map("SRP-DSS-DES-CBC3-SHA") ->
suite_str_to_map("TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA");
-suite_openssl_str_to_map("DHE-RSA-" ++ Rest) ->
+suite_openssl_str_to_map("ADH" ++ Rest) ->
+ suite_openssl_str_to_map("DH-anon", Rest);
+suite_openssl_str_to_map("AECDH" ++ Rest) ->
+ suite_openssl_str_to_map("ECDH-anon", Rest);
+suite_openssl_str_to_map("EDH-RSA" ++ Rest) ->
suite_openssl_str_to_map("DHE-RSA", Rest);
-suite_openssl_str_to_map("DHE-DSS-" ++ Rest) ->
+suite_openssl_str_to_map("EDH-DSS-" ++ Rest) ->
suite_openssl_str_to_map("DHE-DSS", Rest);
-suite_openssl_str_to_map("EDH-RSA-" ++ Rest) ->
+suite_openssl_str_to_map("DHE-RSA-" ++ Rest) ->
suite_openssl_str_to_map("DHE-RSA", Rest);
-suite_openssl_str_to_map("EDH-DSS-" ++ Rest) ->
+suite_openssl_str_to_map("DHE-DSS-" ++ Rest) ->
suite_openssl_str_to_map("DHE-DSS", Rest);
+suite_openssl_str_to_map("DHE-PSK-" ++ Rest) ->
+ suite_openssl_str_to_map("DHE-PSK", Rest);
suite_openssl_str_to_map("DES" ++ _ = Rest) ->
suite_openssl_str_to_map("RSA", Rest);
suite_openssl_str_to_map("AES" ++ _ = Rest) ->
@@ -174,8 +184,6 @@ suite_openssl_str_to_map("RSA-PSK-" ++ Rest) ->
suite_openssl_str_to_map("RSA-PSK", Rest);
suite_openssl_str_to_map("RSA-" ++ Rest) ->
suite_openssl_str_to_map("RSA", Rest);
-suite_openssl_str_to_map("DHE-PSK-" ++ Rest) ->
- suite_openssl_str_to_map("DHE-PSK", Rest);
suite_openssl_str_to_map("ECDHE-PSK-" ++ Rest) ->
suite_openssl_str_to_map("ECDHE-PSK", Rest);
suite_openssl_str_to_map("PSK-" ++ Rest) ->
@@ -348,12 +356,12 @@ suite_bin_to_map(?TLS_DH_anon_WITH_AES_128_CBC_SHA256) ->
#{key_exchange => dh_anon,
cipher => aes_128_cbc,
mac => sha256,
- prf => default_prf};
+ prf => sha256};
suite_bin_to_map(?TLS_DH_anon_WITH_AES_256_CBC_SHA256) ->
#{key_exchange => dh_anon,
cipher => aes_256_cbc,
mac => sha256,
- prf => default_prf};
+ prf => sha256};
%%% PSK Cipher Suites RFC 4279
suite_bin_to_map(?TLS_PSK_WITH_RC4_128_SHA) ->
#{key_exchange => psk,
@@ -466,7 +474,7 @@ suite_bin_to_map(?TLS_PSK_WITH_AES_128_CBC_SHA256) ->
#{key_exchange => psk,
cipher => aes_128_cbc,
mac => sha256,
- prf => default_prf};
+ prf => sha256};
suite_bin_to_map(?TLS_PSK_WITH_AES_256_CBC_SHA384) ->
#{key_exchange => psk,
cipher => aes_256_cbc,
@@ -476,7 +484,7 @@ suite_bin_to_map(?TLS_DHE_PSK_WITH_AES_128_CBC_SHA256) ->
#{key_exchange => dhe_psk,
cipher => aes_128_cbc,
mac => sha256,
- prf => default_prf};
+ prf => sha256};
suite_bin_to_map(?TLS_DHE_PSK_WITH_AES_256_CBC_SHA384) ->
#{key_exchange => dhe_psk,
cipher => aes_256_cbc,
@@ -506,7 +514,7 @@ suite_bin_to_map(?TLS_DHE_PSK_WITH_NULL_SHA256) ->
#{key_exchange => dhe_psk,
cipher => null,
mac => sha256,
- prf => default_prf};
+ prf => sha256};
suite_bin_to_map(?TLS_DHE_PSK_WITH_NULL_SHA384) ->
#{key_exchange => dhe_psk,
cipher => null,
@@ -516,7 +524,7 @@ suite_bin_to_map(?TLS_RSA_PSK_WITH_NULL_SHA256) ->
#{key_exchange => rsa_psk,
cipher => null,
mac => sha256,
- prf => default_prf};
+ prf => sha256};
suite_bin_to_map(?TLS_RSA_PSK_WITH_NULL_SHA384) ->
#{key_exchange => rsa_psk,
cipher => null,
@@ -547,7 +555,7 @@ suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256) ->
#{key_exchange => ecdhe_psk,
cipher => aes_128_cbc,
mac => sha256,
- prf => default_prf};
+ prf => sha256};
suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384) ->
#{key_exchange => ecdhe_psk,
cipher => aes_256_cbc,
@@ -557,7 +565,7 @@ suite_bin_to_map(?TLS_ECDHE_PSK_WITH_NULL_SHA256) ->
#{key_exchange => ecdhe_psk,
cipher => null,
mac => sha256,
- prf => default_prf};
+ prf => sha256};
suite_bin_to_map(?TLS_ECDHE_PSK_WITH_NULL_SHA384) ->
#{key_exchange => ecdhe_psk,
cipher => null, mac => sha384,
@@ -566,22 +574,22 @@ suite_bin_to_map(?TLS_ECDHE_PSK_WITH_NULL_SHA384) ->
suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256) ->
#{key_exchange => ecdhe_psk,
cipher => aes_128_gcm,
- mac => null,
+ mac => aead,
prf => sha256};
suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384) ->
#{key_exchange => ecdhe_psk,
cipher => aes_256_gcm,
- mac => null,
+ mac => aead,
prf => sha384};
suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256) ->
#{key_exchange => ecdhe_psk,
cipher => aes_128_ccm,
- mac => null,
+ mac => aead,
prf => sha256};
suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256) ->
#{key_exchange => ecdhe_psk,
cipher => aes_128_ccm_8,
- mac => null,
+ mac => aead,
prf => sha256};
%%% SRP Cipher Suites RFC 5054
suite_bin_to_map(?TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA) ->
@@ -680,6 +688,26 @@ suite_bin_to_map(?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA) ->
cipher => aes_256_cbc,
mac => sha,
prf => default_prf};
+suite_bin_to_map(?TLS_ECDHE_ECDSA_WITH_AES_128_CCM) ->
+ #{key_exchange => ecdhe_ecdsa,
+ cipher => aes_128_ccm,
+ mac => aead,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDHE_ECDSA_WITH_AES_256_CCM) ->
+ #{key_exchange => ecdhe_ecdsa,
+ cipher => aes_256_ccm,
+ mac => aead,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8) ->
+ #{key_exchange => ecdhe_ecdsa,
+ cipher => aes_128_ccm_8,
+ mac => aead,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8) ->
+ #{key_exchange => ecdhe_ecdsa,
+ cipher => aes_256_ccm_8,
+ mac => aead,
+ prf => default_prf};
suite_bin_to_map(?TLS_ECDH_RSA_WITH_NULL_SHA) ->
#{key_exchange => ecdh_rsa,
cipher => null,
@@ -840,7 +868,7 @@ suite_bin_to_map(?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384) ->
suite_bin_to_map(?TLS_DH_DSS_WITH_AES_128_GCM_SHA256) ->
#{key_exchange => dh_dss,
cipher => aes_128_gcm,
- mac => null,
+ mac => aead,
prf => sha256};
suite_bin_to_map(?TLS_DH_DSS_WITH_AES_256_GCM_SHA384) ->
#{key_exchange => dh_dss,
@@ -902,42 +930,42 @@ suite_bin_to_map(?TLS_PSK_WITH_AES_128_CCM) ->
#{key_exchange => psk,
cipher => aes_128_ccm,
mac => aead,
- prf => sha256};
+ prf => default_prf};
suite_bin_to_map(?TLS_PSK_WITH_AES_256_CCM) ->
#{key_exchange => psk,
cipher => aes_256_ccm,
mac => aead,
- prf => sha256};
+ prf => default_prf};
suite_bin_to_map(?TLS_DHE_PSK_WITH_AES_128_CCM) ->
#{key_exchange => dhe_psk,
cipher => aes_128_ccm,
mac => aead,
- prf => sha256};
+ prf => default_prf};
suite_bin_to_map(?TLS_DHE_PSK_WITH_AES_256_CCM) ->
#{key_exchange => dhe_psk,
cipher => aes_256_ccm,
mac => aead,
- prf => sha256};
+ prf => default_prf};
suite_bin_to_map(?TLS_PSK_WITH_AES_128_CCM_8) ->
#{key_exchange => psk,
cipher => aes_128_ccm_8,
mac => aead,
- prf => sha256};
+ prf => default_prf};
suite_bin_to_map(?TLS_PSK_WITH_AES_256_CCM_8) ->
#{key_exchange => psk,
cipher => aes_256_ccm_8,
mac => aead,
- prf => sha256};
+ prf => default_prf};
suite_bin_to_map(?TLS_PSK_DHE_WITH_AES_128_CCM_8) ->
#{key_exchange => dhe_psk,
cipher => aes_128_ccm_8,
mac => aead,
- prf => sha256};
+ prf => default_prf};
suite_bin_to_map(?TLS_PSK_DHE_WITH_AES_256_CCM_8) ->
#{key_exchange => dhe_psk,
cipher => aes_256_ccm_8,
mac => aead,
- prf => sha256};
+ prf => default_prf};
suite_bin_to_map(#{key_exchange := psk_dhe,
cipher := aes_256_ccm_8,
mac := aead,
@@ -1297,22 +1325,22 @@ suite_map_to_bin(#{key_exchange := ecdhe_psk,
%%% ECDHE_PSK with AES-GCM and AES-CCM Cipher Suites, draft-ietf-tls-ecdhe-psk-aead-05
suite_map_to_bin(#{key_exchange := ecdhe_psk,
cipher := aes_128_gcm,
- mac := null,
+ mac := aead,
prf := sha256}) ->
?TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256;
suite_map_to_bin(#{key_exchange := ecdhe_psk,
cipher := aes_256_gcm,
- mac := null,
+ mac := aead,
prf := sha384}) ->
?TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384;
suite_map_to_bin(#{key_exchange := ecdhe_psk,
cipher := aes_128_ccm_8,
- mac := null,
+ mac := aead,
prf := sha256}) ->
?TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256;
suite_map_to_bin(#{key_exchange := ecdhe_psk,
cipher := aes_128_ccm,
- mac := null,
+ mac := aead,
prf := sha256}) ->
?TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256;
%%% SRP Cipher Suites RFC 5054
@@ -1393,6 +1421,22 @@ suite_map_to_bin(#{key_exchange := ecdhe_ecdsa,
cipher := aes_256_cbc,
mac := sha}) ->
?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA;
+suite_map_to_bin(#{key_exchange := ecdhe_ecdsa,
+ cipher := aes_128_ccm,
+ mac := aead}) ->
+ ?TLS_ECDHE_ECDSA_WITH_AES_128_CCM;
+suite_map_to_bin(#{key_exchange := ecdhe_ecdsa,
+ cipher := aes_256_ccm,
+ mac := aead}) ->
+ ?TLS_ECDHE_ECDSA_WITH_AES_256_CCM;
+suite_map_to_bin(#{key_exchange := ecdhe_ecdsa,
+ cipher := aes_128_ccm_8,
+ mac := aead}) ->
+ ?TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8;
+suite_map_to_bin(#{key_exchange := ecdhe_ecdsa,
+ cipher := aes_256_ccm_8,
+ mac := aead}) ->
+ ?TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8;
suite_map_to_bin(#{key_exchange := ecdh_rsa,
cipher := null,
mac := sha}) ->
@@ -1616,22 +1660,22 @@ suite_map_to_bin(#{key_exchange := dhe_rsa,
suite_map_to_bin(#{key_exchange := psk,
cipher := aes_128_ccm,
mac := aead,
- prf := sha256}) ->
+ prf := default_prf}) ->
?TLS_PSK_WITH_AES_128_CCM;
suite_map_to_bin(#{key_exchange := psk,
cipher := aes_256_ccm,
mac := aead,
- prf := sha256}) ->
+ prf := default_prf}) ->
?TLS_PSK_WITH_AES_256_CCM;
suite_map_to_bin(#{key_exchange := dhe_psk,
cipher := aes_128_ccm,
mac := aead,
- prf := sha256}) ->
+ prf := default_prf}) ->
?TLS_DHE_PSK_WITH_AES_128_CCM;
suite_map_to_bin(#{key_exchange := dhe_psk,
cipher := aes_256_ccm,
mac := aead,
- prf := sha256}) ->
+ prf := default_prf}) ->
?TLS_DHE_PSK_WITH_AES_256_CCM;
suite_map_to_bin(#{key_exchange := rsa,
cipher := aes_128_ccm,
@@ -1641,7 +1685,7 @@ suite_map_to_bin(#{key_exchange := rsa,
suite_map_to_bin(#{key_exchange := rsa,
cipher := aes_256_ccm,
mac := aead,
- prf := sha256}) ->
+ prf := default_prf}) ->
?TLS_RSA_WITH_AES_256_CCM;
suite_map_to_bin(#{key_exchange := dhe_rsa,
cipher := aes_128_ccm,
@@ -1651,48 +1695,48 @@ suite_map_to_bin(#{key_exchange := dhe_rsa,
suite_map_to_bin(#{key_exchange := dhe_rsa,
cipher := aes_256_ccm,
mac := aead,
- prf := sha256}) ->
+ prf := default_prf}) ->
?TLS_DHE_RSA_WITH_AES_256_CCM;
suite_map_to_bin(#{key_exchange := psk,
cipher := aes_128_ccm_8,
mac := aead,
- prf := sha256}) ->
+ prf := default_prf}) ->
?TLS_PSK_WITH_AES_128_CCM_8;
suite_map_to_bin(#{key_exchange := psk,
cipher := aes_256_ccm_8,
mac := aead,
- prf := sha256}) ->
+ prf := default_prf}) ->
?TLS_PSK_WITH_AES_256_CCM_8;
suite_map_to_bin(#{key_exchange := dhe_psk,
cipher := aes_128_ccm_8,
mac := aead,
- prf := sha256}) ->
+ prf := default_prf}) ->
?TLS_PSK_DHE_WITH_AES_128_CCM_8;
suite_map_to_bin(#{key_exchange := dhe_psk,
cipher := aes_256_ccm_8,
mac := aead,
- prf := sha256}) ->
+ prf := default_prf}) ->
?TLS_PSK_DHE_WITH_AES_256_CCM_8;
suite_map_to_bin(#{key_exchange := rsa,
cipher := aes_128_ccm_8,
mac := aead,
- prf := sha256}) ->
+ prf := default_prf}) ->
?TLS_RSA_WITH_AES_128_CCM_8;
suite_map_to_bin(#{key_exchange := rsa,
cipher := aes_256_ccm_8,
mac := aead,
- prf := sha256}) ->
+ prf := default_prf}) ->
?TLS_RSA_WITH_AES_256_CCM_8;
suite_map_to_bin(#{key_exchange := dhe_rsa,
cipher := aes_128_ccm_8,
mac := aead,
- prf := sha256}) ->
+ prf := default_prf}) ->
?TLS_DHE_RSA_WITH_AES_128_CCM_8;
suite_map_to_bin(#{key_exchange := dhe_rsa,
cipher := aes_256_ccm_8,
mac := aead,
- prf := sha256}) ->
+ prf := default_prf}) ->
?TLS_DHE_RSA_WITH_AES_256_CCM_8;
%% TLS 1.3 Cipher Suites RFC8446
@@ -1740,21 +1784,42 @@ pre_tls_1_3_suite_str_to_map(KexStr, Rest) ->
cipher => Cipher,
prf => Prf
}.
-
-cipher_str_to_algs(_, CipherStr, "CCM"= End) -> %% PRE TLS 1.3
- Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End),
- {Cipher, aead, sha256};
-cipher_str_to_algs(_, CipherStr, "8" = End) -> %% PRE TLS 1.3
- Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End),
- {Cipher, aead, sha256};
-cipher_str_to_algs(_, CipherStr, "CHACHA20_POLY1305" = End) -> %% PRE TLS 1.3
- Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End),
- {Cipher, aead, sha256};
-cipher_str_to_algs(_, CipherStr0, "") -> %% TLS 1.3
+
+kex_str(srp_dss) ->
+ "SRP_SHA_DSS";
+kex_str(srp_rsa) ->
+ "SRP_SHA_RSA";
+kex_str(srp_anon) ->
+ "SRP_SHA";
+kex_str(dh_anon) ->
+ "DH_anon";
+kex_str(ecdh_anon) ->
+ "ECDH_anon";
+kex_str(Kex) ->
+ string:to_upper(atom_to_list(Kex)).
+
+prf_str(_, default_prf) ->
+ "";
+prf_str(Prefix, PRF) ->
+ Prefix ++ string:to_upper(atom_to_list(PRF)).
+
+cipher_str_to_algs(any, CipherStr0, "") -> %% TLS 1.3
[CipherStr, AlgStr] = string:split(CipherStr0, "_", trailing),
Hash = algo_str_to_atom(AlgStr),
Cipher = algo_str_to_atom(CipherStr),
{Cipher, aead, Hash};
+cipher_str_to_algs(_Kex, CipherStr, "CCM"= End) -> %% PRE TLS 1.3
+ Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End),
+ {Cipher, aead, default_prf};
+cipher_str_to_algs(_Kex, CipherStr, "GCM"= End) -> %% PRE TLS 1.3
+ Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End),
+ {Cipher, aead, default_prf};
+cipher_str_to_algs(_Kex, CipherStr, "8" = End) -> %% PRE TLS 1.3
+ Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End),
+ {Cipher, aead, default_prf};
+cipher_str_to_algs(_Kex, "CHACHA20_POLY1305" = CipherStr, "") -> %% PRE TLS 1.3
+ Cipher = algo_str_to_atom(CipherStr),
+ {Cipher, aead, sha256};
cipher_str_to_algs(Kex, CipherStr, HashStr) -> %% PRE TLS 1.3
Hash = algo_str_to_atom(HashStr),
Cipher = algo_str_to_atom(CipherStr),
@@ -1796,66 +1861,111 @@ openssl_is_aead_cipher("CHACHA20-POLY1305") ->
openssl_is_aead_cipher(CipherStr) ->
case string:split(CipherStr, "-", trailing) of
[_, Rest] ->
- (Rest == "GCM") orelse (Rest == "CCM") orelse (Rest == "8");
+ (Rest == "GCM") orelse (Rest == "CCM") orelse (Rest == "CCM8");
[_] ->
false
end.
algo_str_to_atom("SRP_SHA_DSS") ->
srp_dss;
+algo_str_to_atom("SRP_SHA_RSA") ->
+ srp_rsa;
+algo_str_to_atom("SRP_SHA") ->
+ srp_anon;
+algo_str_to_atom("SRP") ->
+ srp_anon;
algo_str_to_atom(AlgoStr) ->
erlang:list_to_existing_atom(string:to_lower(AlgoStr)).
+openssl_cipher_name(Kex, "3DES_EDE_CBC" ++ _) when Kex == ecdhe_psk;
+ Kex == srp_anon;
+ Kex == psk;
+ Kex == dhe_psk ->
+ "3DES-EDE-CBC";
openssl_cipher_name(_, "3DES_EDE_CBC" ++ _) ->
"DES-CBC3";
openssl_cipher_name(Kex, "AES_128_CBC" ++ _ = CipherStr) when Kex == rsa;
Kex == dhe_rsa;
+ Kex == dhe_dss;
+ Kex == ecdh_rsa;
Kex == ecdhe_rsa;
- Kex == ecdhe_ecdsa ->
+ Kex == ecdh_ecdsa;
+ Kex == ecdhe_ecdsa;
+ Kex == ecdh_anon;
+ Kex == dh_anon ->
openssl_name_concat(CipherStr);
openssl_cipher_name(Kex, "AES_256_CBC" ++ _ = CipherStr) when Kex == rsa;
Kex == dhe_rsa;
+ Kex == dhe_dss;
+ Kex == ecdh_rsa;
Kex == ecdhe_rsa;
- Kex == ecdhe_ecdsa ->
+ Kex == ecdh_ecdsa;
+ Kex == ecdhe_ecdsa;
+ Kex == ecdh_anon;
+ Kex == dh_anon ->
openssl_name_concat(CipherStr);
-openssl_cipher_name(Kex, "AES_128_CBC" ++ _ = CipherStr) when Kex == srp;
+openssl_cipher_name(Kex, "AES_128_CBC" ++ _ = CipherStr) when Kex == srp_anon;
Kex == srp_rsa ->
lists:append(string:replace(CipherStr, "_", "-", all));
-openssl_cipher_name(Kex, "AES_256_CBC" ++ _ = CipherStr) when Kex == srp;
+openssl_cipher_name(Kex, "AES_256_CBC" ++ _ = CipherStr) when Kex == srp_anon;
Kex == srp_rsa ->
lists:append(string:replace(CipherStr, "_", "-", all));
openssl_cipher_name(_, "AES_128_CBC" ++ _ = CipherStr) ->
openssl_name_concat(CipherStr) ++ "-CBC";
openssl_cipher_name(_, "AES_256_CBC" ++ _ = CipherStr) ->
openssl_name_concat(CipherStr) ++ "-CBC";
+openssl_cipher_name(_, "AES_128_GCM_8") ->
+ openssl_name_concat("AES_128_GCM") ++ "-GCM8";
+openssl_cipher_name(_, "AES_256_GCM_8") ->
+ openssl_name_concat("AES_256_GCM") ++ "-GCM8";
+openssl_cipher_name(_, "AES_128_CCM_8") ->
+ openssl_name_concat("AES_128_CCM") ++ "-CCM8";
+openssl_cipher_name(_, "AES_256_CCM_8") ->
+ openssl_name_concat("AES_256_CCM") ++ "-CCM8";
openssl_cipher_name(_, "AES_128_GCM" ++ _ = CipherStr) ->
openssl_name_concat(CipherStr) ++ "-GCM";
openssl_cipher_name(_, "AES_256_GCM" ++ _ = CipherStr) ->
openssl_name_concat(CipherStr) ++ "-GCM";
+openssl_cipher_name(_, "AES_128_CCM" ++ _ = CipherStr) ->
+ openssl_name_concat(CipherStr) ++ "-CCM";
+openssl_cipher_name(_, "AES_256_CCM" ++ _ = CipherStr) ->
+ openssl_name_concat(CipherStr) ++ "-CCM";
openssl_cipher_name(_, "RC4" ++ _) ->
"RC4";
openssl_cipher_name(_, CipherStr) ->
lists:append(string:replace(CipherStr, "_", "-", all)).
-
-openssl_suite_start(Kex) ->
- case openssl_kex_name(Kex) of
+openssl_suite_start(Kex, Cipher) ->
+ case openssl_kex_name(Kex, Cipher) of
"" ->
"";
Name ->
Name ++ "-"
end.
-openssl_kex_name("RSA") ->
+openssl_kex_name("RSA", _) ->
"";
-openssl_kex_name("DHE_RSA") ->
+openssl_kex_name("DH_anon", _) ->
+ "ADH";
+openssl_kex_name("ECDH_anon", _) ->
+ "AECDH";
+openssl_kex_name("SRP_SHA", _) ->
+ "SRP";
+openssl_kex_name("SRP_SHA_RSA", _) ->
+ "SRP-RSA";
+openssl_kex_name("SRP_SHA_DSS", _) ->
+ "SRP-DSS";
+openssl_kex_name("DHE_RSA", Cipher) when Cipher == des_cbc;
+ Cipher == '3des_ede_cbc' ->
"EDH-RSA";
-openssl_kex_name(Kex) ->
+openssl_kex_name(Kex, _) ->
lists:append(string:replace(Kex, "_", "-", all)).
kex_name_from_openssl(Kex) ->
case lists:append(string:replace(Kex, "-", "_", all)) of
- "EDH_RSA" ->
- "DHE_RSA";
+ "EDH-RSA" ->
+ "DHE_RSA";
+ "SRP" ->
+ "SRP_SHA";
Str ->
Str
end.
@@ -1864,26 +1974,30 @@ cipher_name_from_openssl("AES128") ->
"AES_128_CBC";
cipher_name_from_openssl("AES256") ->
"AES_256_CBC";
-cipher_name_from_openssl("AES128-CBC") ->
- "AES_128_CBC";
-cipher_name_from_openssl("AES256-CBC") ->
- "AES_256_CBC";
-cipher_name_from_openssl("AES-128-CBC") ->
- "AES_128_CBC";
-cipher_name_from_openssl("AES-256-CBC") ->
- "AES_256_CBC";
-cipher_name_from_openssl("AES128-GCM") ->
- "AES_128_GCM";
-cipher_name_from_openssl("AES256-GCM") ->
- "AES_256_GCM";
+cipher_name_from_openssl("AES128-CCM8") ->
+ "AES_128_CCM_8";
+cipher_name_from_openssl("AES256-CCM8") ->
+ "AES_256_CCM_8";
+cipher_name_from_openssl("AES128-" ++ Suffix) ->
+ "AES_128_" ++ lists:append(string:replace(Suffix, "-", "_", all));
+cipher_name_from_openssl("AES256-" ++ Suffix) ->
+ "AES_256_" ++ lists:append(string:replace(Suffix, "-", "_", all));
+cipher_name_from_openssl("AES128_" ++ Suffix) ->
+ "AES_128_" ++ Suffix;
+cipher_name_from_openssl("AES256_" ++ Suffix) ->
+ "AES_256_" ++ Suffix;
cipher_name_from_openssl("DES-CBC") ->
"DES_CBC";
cipher_name_from_openssl("DES-CBC3") ->
"3DES_EDE_CBC";
+cipher_name_from_openssl("3DES-EDE-CBC") ->
+ "3DES_EDE_CBC";
cipher_name_from_openssl("RC4") ->
"RC4_128";
+cipher_name_from_openssl("CHACHA20-POLY1305") ->
+ "CHACHA20_POLY1305";
cipher_name_from_openssl(Str) ->
- Str.
+ lists:append(string:replace(Str, "-", "_", all)).
openssl_name_concat(Str0) ->
[Str, _] = string:split(Str0, "_", trailing),
@@ -1893,8 +2007,8 @@ openssl_name_concat(Str0) ->
suite_openssl_str_to_map(Kex0, Rest) ->
Kex = algo_str_to_atom(kex_name_from_openssl(Kex0)),
- [CipherStr, AlgStr] = string:split(Rest, "-", trailing),
- {Cipher, Mac, Prf} = openssl_cipher_str_to_algs(Kex, CipherStr, AlgStr),
+ [Part1, Part2] = string:split(Rest, "-", trailing),
+ {Cipher, Mac, Prf} = openssl_cipher_str_to_algs(Kex, Part1, Part2),
#{key_exchange => Kex,
mac => Mac,
cipher => Cipher,
@@ -1902,19 +2016,25 @@ suite_openssl_str_to_map(Kex0, Rest) ->
}.
%% Does only need own implementation PRE TLS 1.3
-openssl_cipher_str_to_algs(_, CipherStr, "CCM"= End) ->
- Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End),
- {Cipher, aead, sha256};
-openssl_cipher_str_to_algs(_, CipherStr, "8" = End) ->
- Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End),
+openssl_cipher_str_to_algs(_, Part1, "CCM" = End) ->
+ Cipher = algo_str_to_atom(cipher_name_from_openssl(Part1 ++ "_" ++ End)),
+ {Cipher, aead, default_prf};
+openssl_cipher_str_to_algs(_, Part1, "GCM" = End) ->
+ Cipher = algo_str_to_atom(cipher_name_from_openssl(Part1 ++ "_" ++ End)),
+ {Cipher, aead, default_prf};
+openssl_cipher_str_to_algs(_, Part2, "CCM8") ->
+ Cipher = algo_str_to_atom(cipher_name_from_openssl(Part2 ++ "-CCM-8")),
+ {Cipher, aead, default_prf};
+openssl_cipher_str_to_algs(_, Part2, "GCM8") ->
+ Cipher = algo_str_to_atom(cipher_name_from_openssl(Part2 ++ "-GCM-8")),
+ {Cipher, aead, default_prf};
+openssl_cipher_str_to_algs(_, "CHACHA20", "POLY1305") ->
+ Cipher = chacha20_poly1305,
{Cipher, aead, sha256};
-openssl_cipher_str_to_algs(_, CipherStr, "POLY1305" = End) ->
- Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End),
- {Cipher, aead, sha256};
-openssl_cipher_str_to_algs(Kex, CipherStr, HashStr) ->
- Hash = algo_str_to_atom(HashStr),
- Cipher = algo_str_to_atom(cipher_name_from_openssl(CipherStr)),
- case openssl_is_aead_cipher(CipherStr) of
+openssl_cipher_str_to_algs(Kex, Part1, Part2) ->
+ Hash = algo_str_to_atom(Part2),
+ Cipher = algo_str_to_atom(cipher_name_from_openssl(string:strip(Part1, left, $-))),
+ case openssl_is_aead_cipher(Part1) of
true ->
{Cipher, aead, Hash};
false ->
diff --git a/lib/ssl/src/ssl_config.erl b/lib/ssl/src/ssl_config.erl
index 10f95d5b3c..6d09af9b1c 100644
--- a/lib/ssl/src/ssl_config.erl
+++ b/lib/ssl/src/ssl_config.erl
@@ -26,8 +26,15 @@
-include("ssl_connection.hrl").
-include_lib("public_key/include/public_key.hrl").
--export([init/2]).
+-define(DEFAULT_MAX_SESSION_CACHE, 1000).
+-export([init/2,
+ pre_1_3_session_opts/0
+ ]).
+
+%%====================================================================
+%% Internal application API
+%%====================================================================
init(#{erl_dist := ErlDist,
key := Key,
keyfile := KeyFile,
@@ -44,6 +51,24 @@ init(#{erl_dist := ErlDist,
DHParams = init_diffie_hellman(PemCache, DH, DHFile, Role),
{ok, Config#{private_key => PrivateKey, dh_params => DHParams}}.
+pre_1_3_session_opts() ->
+ CbOpts = case application:get_env(ssl, session_cb) of
+ {ok, Cb} when is_atom(Cb) ->
+ InitArgs = session_cb_init_args(),
+ #{session_cb => Cb,
+ session_cb_init_args => InitArgs};
+ _ ->
+ #{session_cb => ssl_server_session_cache_db,
+ session_cb_init_args => []}
+ end,
+ LifeTime = session_lifetime(),
+ Max = max_session_cache_size(),
+ [CbOpts#{lifetime => LifeTime, max => Max}].
+
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
init_manager_name(false) ->
put(ssl_manager, ssl_manager:name(normal)),
put(ssl_pem_cache, ssl_pem_cache:name(normal));
@@ -54,7 +79,7 @@ init_manager_name(true) ->
init_certificates(#{cacerts := CaCerts,
cacertfile := CACertFile,
certfile := CertFile,
- cert := Cert,
+ cert := OwnCerts,
crl_cache := CRLCache
}, Role) ->
{ok, Config} =
@@ -70,31 +95,31 @@ init_certificates(#{cacerts := CaCerts,
_:Reason ->
file_error(CACertFile, {cacertfile, Reason})
end,
- init_certificates(Cert, Config, CertFile, Role).
+ init_certificates(OwnCerts, Config, CertFile, Role).
init_certificates(undefined, Config, <<>>, _) ->
- {ok, Config#{own_certificate => undefined}};
+ {ok, Config#{own_certificates => undefined}};
init_certificates(undefined, #{pem_cache := PemCache} = Config, CertFile, client) ->
try
- %% Ignoring potential proxy-certificates see:
- %% http://dev.globus.org/wiki/Security/ProxyFileFormat
- [OwnCert|_] = ssl_certificate:file_to_certificats(CertFile, PemCache),
- {ok, Config#{own_certificate => OwnCert}}
+ %% OwnCert | [OwnCert | Chain]
+ OwnCerts = ssl_certificate:file_to_certificats(CertFile, PemCache),
+ {ok, Config#{own_certificates => OwnCerts}}
catch _Error:_Reason ->
- {ok, Config#{own_certificate => undefined}}
+ {ok, Config#{own_certificates => undefined}}
end;
init_certificates(undefined, #{pem_cache := PemCache} = Config, CertFile, server) ->
try
- [OwnCert|_] = ssl_certificate:file_to_certificats(CertFile, PemCache),
- {ok, Config#{own_certificate => OwnCert}}
+ %% OwnCert | [OwnCert | Chain]
+ OwnCerts = ssl_certificate:file_to_certificats(CertFile, PemCache),
+ {ok, Config#{own_certificates => OwnCerts}}
catch
_:Reason ->
file_error(CertFile, {certfile, Reason})
end;
-init_certificates(Cert, Config, _, _) ->
- {ok, Config#{own_certificate => Cert}}.
+init_certificates(OwnCerts, Config, _, _) ->
+ {ok, Config#{own_certificates => OwnCerts}}.
init_private_key(_, #{algorithm := Alg} = Key, _, _Password, _Client) when Alg == ecdsa;
Alg == rsa;
Alg == dss ->
@@ -176,3 +201,27 @@ init_diffie_hellman(DbHandle,_, DHParamFile, server) ->
_:Reason ->
file_error(DHParamFile, {dhfile, Reason})
end.
+
+session_cb_init_args() ->
+ case application:get_env(ssl, session_cb_init_args) of
+ {ok, Args} when is_list(Args) ->
+ Args;
+ _ ->
+ []
+ end.
+
+session_lifetime() ->
+ case application:get_env(ssl, session_lifetime) of
+ {ok, Time} when is_integer(Time) ->
+ Time;
+ _ ->
+ ?'24H_in_sec'
+ end.
+
+max_session_cache_size() ->
+ case application:get_env(ssl, session_cache_server_max) of
+ {ok, Size} when is_integer(Size) ->
+ Size;
+ _ ->
+ ?DEFAULT_MAX_SESSION_CACHE
+ end.
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
deleted file mode 100644
index a854f50ee9..0000000000
--- a/lib/ssl/src/ssl_connection.erl
+++ /dev/null
@@ -1,3182 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2013-2020. All Rights Reserved.
-%%
-%% Licensed under the Apache License, Version 2.0 (the "License");
-%% you may not use this file except in compliance with the License.
-%% You may obtain a copy of the License at
-%%
-%% http://www.apache.org/licenses/LICENSE-2.0
-%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-%% See the License for the specific language governing permissions and
-%% limitations under the License.
-%%
-%% %CopyrightEnd%
-%%
-
-%%
-%%----------------------------------------------------------------------
-%% Purpose: Common handling of a TLS/SSL/DTLS connection, see also
-%% tls_connection.erl and dtls_connection.erl
-%%----------------------------------------------------------------------
-
--module(ssl_connection).
-
--include("ssl_api.hrl").
--include("ssl_connection.hrl").
--include("ssl_handshake.hrl").
--include("ssl_alert.hrl").
--include("ssl_record.hrl").
--include("ssl_cipher.hrl").
--include("ssl_internal.hrl").
--include("ssl_srp.hrl").
--include_lib("public_key/include/public_key.hrl").
--include_lib("kernel/include/logger.hrl").
-
-%% Setup
-
--export([connect/8, handshake/7, handshake/2, handshake/3, handle_common_event/5,
- handshake_continue/3, handshake_cancel/1,
- socket_control/4, socket_control/5]).
-
-%% User Events
--export([send/2, recv/3, close/2, shutdown/2,
- new_user/2, get_opts/2, set_opts/2,
- peer_certificate/1, renegotiation/1, negotiated_protocol/1, prf/5,
- connection_information/2
- ]).
-
-%% Alert and close handling
--export([handle_own_alert/4, handle_alert/3,
- handle_normal_shutdown/3,
- handle_trusted_certs_db/1,
- maybe_invalidate_session/6]).
-
-%% Data handling
--export([read_application_data/2, internal_renegotiation/2]).
-
-%% Help functions for tls|dtls_connection.erl
--export([handle_session/7, ssl_config/3,
- prepare_connection/2, hibernate_after/3]).
-
-%% General gen_statem state functions with extra callback argument
-%% to determine if it is an SSL/TLS or DTLS gen_statem machine
--export([init/4, error/4, hello/4, user_hello/4, abbreviated/4, certify/4, wait_ocsp_stapling/4, cipher/4,
- connection/4, downgrade/4]).
-
-%% gen_statem callbacks
--export([terminate/3, format_status/2]).
-
-%% Erlang Distribution export
--export([dist_handshake_complete/2]).
-
-%%====================================================================
-%% Setup
-%%====================================================================
-%%--------------------------------------------------------------------
--spec connect(tls_connection | dtls_connection,
- ssl:host(), inet:port_number(),
- port() | {tuple(), port()}, %% TLS | DTLS
- {ssl_options(), #socket_options{},
- %% Tracker only needed on server side
- undefined},
- pid(), tuple(), timeout()) ->
- {ok, #sslsocket{}} | {error, reason()}.
-%%
-%% Description: Connect to an ssl server.
-%%--------------------------------------------------------------------
-connect(Connection, Host, Port, Socket, Options, User, CbInfo, Timeout) ->
- try Connection:start_fsm(client, Host, Port, Socket, Options, User, CbInfo,
- Timeout)
- catch
- exit:{noproc, _} ->
- {error, ssl_not_started}
- end.
-%%--------------------------------------------------------------------
--spec handshake(tls_connection | dtls_connection,
- inet:port_number(), port(),
- {ssl_options(), #socket_options{}, list()},
- pid(), tuple(), timeout()) ->
- {ok, #sslsocket{}} | {error, reason()}.
-%%
-%% Description: Performs accept on an ssl listen socket. e.i. performs
-%% ssl handshake.
-%%--------------------------------------------------------------------
-handshake(Connection, Port, Socket, Opts, User, CbInfo, Timeout) ->
- try Connection:start_fsm(server, "localhost", Port, Socket, Opts, User,
- CbInfo, Timeout)
- catch
- exit:{noproc, _} ->
- {error, ssl_not_started}
- end.
-
-%%--------------------------------------------------------------------
--spec handshake(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} |
- {ok, #sslsocket{}, map()}| {error, reason()}.
-%%
-%% Description: Starts ssl handshake.
-%%--------------------------------------------------------------------
-handshake(#sslsocket{pid = [Pid|_]} = Socket, Timeout) ->
- case call(Pid, {start, Timeout}) of
- connected ->
- {ok, Socket};
- {ok, Ext} ->
- {ok, Socket, no_records(Ext)};
- Error ->
- Error
- end.
-
-%%--------------------------------------------------------------------
--spec handshake(#sslsocket{}, {ssl_options(),#socket_options{}}, timeout()) ->
- {ok, #sslsocket{}} | {ok, #sslsocket{}, map()} | {error, reason()}.
-%%
-%% Description: Starts ssl handshake with some new options
-%%--------------------------------------------------------------------
-handshake(#sslsocket{pid = [Pid|_]} = Socket, SslOptions, Timeout) ->
- case call(Pid, {start, SslOptions, Timeout}) of
- connected ->
- {ok, Socket};
- Error ->
- Error
- end.
-
-%%--------------------------------------------------------------------
--spec handshake_continue(#sslsocket{}, [ssl:tls_server_option()],
- timeout()) -> {ok, #sslsocket{}}| {error, reason()}.
-%%
-%% Description: Continues handshake with new options
-%%--------------------------------------------------------------------
-handshake_continue(#sslsocket{pid = [Pid|_]} = Socket, SslOptions, Timeout) ->
- case call(Pid, {handshake_continue, SslOptions, Timeout}) of
- connected ->
- {ok, Socket};
- Error ->
- Error
- end.
-%%--------------------------------------------------------------------
--spec handshake_cancel(#sslsocket{}) -> ok | {error, reason()}.
-%%
-%% Description: Cancels connection
-%%--------------------------------------------------------------------
-handshake_cancel(#sslsocket{pid = [Pid|_]}) ->
- case call(Pid, cancel) of
- closed ->
- ok;
- Error ->
- Error
- end.
-%--------------------------------------------------------------------
--spec socket_control(tls_connection | dtls_connection, port(), [pid()], atom()) ->
- {ok, #sslsocket{}} | {error, reason()}.
-%%
-%% Description: Set the ssl process to own the accept socket
-%%--------------------------------------------------------------------
-socket_control(Connection, Socket, Pid, Transport) ->
- socket_control(Connection, Socket, Pid, Transport, undefined).
-
-%--------------------------------------------------------------------
--spec socket_control(tls_connection | dtls_connection, port(), [pid()], atom(), [pid()] | atom()) ->
- {ok, #sslsocket{}} | {error, reason()}.
-%%--------------------------------------------------------------------
-socket_control(Connection, Socket, Pids, Transport, udp_listener) ->
- %% dtls listener process must have the socket control
- {ok, Connection:socket(Pids, Transport, Socket, undefined)};
-
-socket_control(tls_connection = Connection, Socket, [Pid|_] = Pids, Transport, Trackers) ->
- case Transport:controlling_process(Socket, Pid) of
- ok ->
- {ok, Connection:socket(Pids, Transport, Socket, Trackers)};
- {error, Reason} ->
- {error, Reason}
- end;
-socket_control(dtls_connection = Connection, {_, Socket}, [Pid|_] = Pids, Transport, Trackers) ->
- case Transport:controlling_process(Socket, Pid) of
- ok ->
- {ok, Connection:socket(Pids, Transport, Socket, Trackers)};
- {error, Reason} ->
- {error, Reason}
- end.
-
-
-%%====================================================================
-%% User events
-%%====================================================================
-
-%%--------------------------------------------------------------------
--spec send(pid(), iodata()) -> ok | {error, reason()}.
-%%
-%% Description: Sends data over the ssl connection
-%%--------------------------------------------------------------------
-send(Pid, Data) ->
- call(Pid, {application_data,
- %% iolist_to_iovec should really
- %% be called iodata_to_iovec()
- erlang:iolist_to_iovec(Data)}).
-
-%%--------------------------------------------------------------------
--spec recv(pid(), integer(), timeout()) ->
- {ok, binary() | list()} | {error, reason()}.
-%%
-%% Description: Receives data when active = false
-%%--------------------------------------------------------------------
-recv(Pid, Length, Timeout) ->
- call(Pid, {recv, Length, Timeout}).
-
-%%--------------------------------------------------------------------
--spec connection_information(pid(), boolean()) -> {ok, list()} | {error, reason()}.
-%%
-%% Description: Get the SNI hostname
-%%--------------------------------------------------------------------
-connection_information(Pid, IncludeSecrityInfo) when is_pid(Pid) ->
- call(Pid, {connection_information, IncludeSecrityInfo}).
-
-%%--------------------------------------------------------------------
--spec close(pid(), {close, Timeout::integer() |
- {NewController::pid(), Timeout::integer()}}) ->
- ok | {ok, port()} | {error, reason()}.
-%%
-%% Description: Close an ssl connection
-%%--------------------------------------------------------------------
-close(ConnectionPid, How) ->
- case call(ConnectionPid, How) of
- {error, closed} ->
- ok;
- Other ->
- Other
- end.
-%%--------------------------------------------------------------------
--spec shutdown(pid(), atom()) -> ok | {error, reason()}.
-%%
-%% Description: Same as gen_tcp:shutdown/2
-%%--------------------------------------------------------------------
-shutdown(ConnectionPid, How) ->
- call(ConnectionPid, {shutdown, How}).
-
-%%--------------------------------------------------------------------
--spec new_user(pid(), pid()) -> ok | {error, reason()}.
-%%
-%% Description: Changes process that receives the messages when active = true
-%% or once.
-%%--------------------------------------------------------------------
-new_user(ConnectionPid, User) ->
- call(ConnectionPid, {new_user, User}).
-
-%%--------------------------------------------------------------------
--spec negotiated_protocol(pid()) -> {ok, binary()} | {error, reason()}.
-%%
-%% Description: Returns the negotiated protocol
-%%--------------------------------------------------------------------
-negotiated_protocol(ConnectionPid) ->
- call(ConnectionPid, negotiated_protocol).
-
-%%--------------------------------------------------------------------
--spec get_opts(pid(), list()) -> {ok, list()} | {error, reason()}.
-%%
-%% Description: Same as inet:getopts/2
-%%--------------------------------------------------------------------
-get_opts(ConnectionPid, OptTags) ->
- call(ConnectionPid, {get_opts, OptTags}).
-%%--------------------------------------------------------------------
--spec set_opts(pid(), list()) -> ok | {error, reason()}.
-%%
-%% Description: Same as inet:setopts/2
-%%--------------------------------------------------------------------
-set_opts(ConnectionPid, Options) ->
- call(ConnectionPid, {set_opts, Options}).
-
-%%--------------------------------------------------------------------
--spec peer_certificate(pid()) -> {ok, binary()| undefined} | {error, reason()}.
-%%
-%% Description: Returns the peer cert
-%%--------------------------------------------------------------------
-peer_certificate(ConnectionPid) ->
- call(ConnectionPid, peer_certificate).
-
-%%--------------------------------------------------------------------
--spec renegotiation(pid()) -> ok | {error, reason()}.
-%%
-%% Description: Starts a renegotiation of the ssl session.
-%%--------------------------------------------------------------------
-renegotiation(ConnectionPid) ->
- call(ConnectionPid, renegotiate).
-
-%%--------------------------------------------------------------------
--spec internal_renegotiation(pid(), ssl_record:connection_states()) ->
- ok.
-%%
-%% Description: Starts a renegotiation of the ssl session.
-%%--------------------------------------------------------------------
-internal_renegotiation(ConnectionPid, #{current_write := WriteState}) ->
- gen_statem:cast(ConnectionPid, {internal_renegotiate, WriteState}).
-
-dist_handshake_complete(ConnectionPid, DHandle) ->
- gen_statem:cast(ConnectionPid, {dist_handshake_complete, DHandle}).
-
-%%--------------------------------------------------------------------
--spec prf(pid(), binary() | 'master_secret', binary(),
- [binary() | ssl:prf_random()], non_neg_integer()) ->
- {ok, binary()} | {error, reason()} | {'EXIT', term()}.
-%%
-%% Description: use a ssl sessions TLS PRF to generate key material
-%%--------------------------------------------------------------------
-prf(ConnectionPid, Secret, Label, Seed, WantedLength) ->
- call(ConnectionPid, {prf, Secret, Label, Seed, WantedLength}).
-
-
-%%====================================================================
-%% Alert and close handling
-%%====================================================================
-handle_own_alert(Alert0, _, StateName,
- #state{static_env = #static_env{role = Role,
- protocol_cb = Connection},
- ssl_options = #{log_level := LogLevel}} = State) ->
- try %% Try to tell the other side
- send_alert(Alert0, StateName, State)
- catch _:_ -> %% Can crash if we are in a uninitialized state
- ignore
- end,
- try %% Try to tell the local user
- Alert = Alert0#alert{role = Role},
- log_alert(LogLevel, Role, Connection:protocol_name(), StateName, Alert),
- handle_normal_shutdown(Alert,StateName, State)
- catch _:_ ->
- ok
- end,
- {stop, {shutdown, own_alert}, State}.
-
-handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role,
- socket = Socket,
- transport_cb = Transport,
- protocol_cb = Connection,
- trackers = Trackers},
- handshake_env = #handshake_env{renegotiation = {false, first}},
- start_or_recv_from = StartFrom} = State) ->
- Pids = Connection:pids(State),
- alert_user(Pids, Transport, Trackers, Socket, StartFrom, Alert, Role, StateName, Connection);
-
-handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role,
- socket = Socket,
- transport_cb = Transport,
- protocol_cb = Connection,
- trackers = Trackers},
- connection_env = #connection_env{user_application = {_Mon, Pid}},
- socket_options = Opts,
- start_or_recv_from = RecvFrom} = State) ->
- Pids = Connection:pids(State),
- alert_user(Pids, Transport, Trackers, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role, StateName, Connection).
-
-handle_alert(#alert{level = ?FATAL} = Alert0, StateName,
- #state{static_env = #static_env{role = Role,
- socket = Socket,
- host = Host,
- port = Port,
- trackers = Trackers,
- transport_cb = Transport,
- protocol_cb = Connection},
- connection_env = #connection_env{user_application = {_Mon, Pid}},
- ssl_options = #{log_level := LogLevel},
- start_or_recv_from = From,
- session = Session,
- socket_options = Opts} = State) ->
- invalidate_session(Role, Host, Port, Session),
- Alert = Alert0#alert{role = opposite_role(Role)},
- log_alert(LogLevel, Role, Connection:protocol_name(),
- StateName, Alert),
- Pids = Connection:pids(State),
- alert_user(Pids, Transport, Trackers, Socket, StateName, Opts, Pid, From, Alert, Role, StateName, Connection),
- {stop, {shutdown, normal}, State};
-
-handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert,
- downgrade= StateName, State) ->
- {next_state, StateName, State, [{next_event, internal, Alert}]};
-handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert0,
- StateName, #state{static_env = #static_env{role = Role}} = State) ->
- Alert = Alert0#alert{role = opposite_role(Role)},
- handle_normal_shutdown(Alert, StateName, State),
- {stop,{shutdown, peer_close}, State};
-handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert0, StateName,
- #state{static_env = #static_env{role = Role,
- protocol_cb = Connection},
- handshake_env = #handshake_env{renegotiation = {true, internal}},
- ssl_options = #{log_level := LogLevel}} = State) ->
- Alert = Alert0#alert{role = opposite_role(Role)},
- log_alert(LogLevel, Role,
- Connection:protocol_name(), StateName, Alert),
- handle_normal_shutdown(Alert, StateName, State),
- {stop,{shutdown, peer_close}, State};
-
-handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, connection = StateName,
- #state{static_env = #static_env{role = Role,
- protocol_cb = Connection},
- handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv,
- ssl_options = #{log_level := LogLevel}
- } = State0) ->
- log_alert(LogLevel, Role,
- Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}),
- gen_statem:reply(From, {error, renegotiation_rejected}),
- State = Connection:reinit_handshake_data(State0),
- Connection:next_event(connection, no_record, State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}});
-
-handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName,
- #state{static_env = #static_env{role = Role,
- protocol_cb = Connection},
- handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv,
- ssl_options = #{log_level := LogLevel}
- } = State0) ->
- log_alert(LogLevel, Role,
- Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}),
- gen_statem:reply(From, {error, renegotiation_rejected}),
- %% Go back to connection!
- State = Connection:reinit(State0#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}}),
- Connection:next_event(connection, no_record, State);
-
-%% Gracefully log and ignore all other warning alerts
-handle_alert(#alert{level = ?WARNING} = Alert, StateName,
- #state{static_env = #static_env{role = Role,
- protocol_cb = Connection},
- ssl_options = #{log_level := LogLevel}} = State) ->
- log_alert(LogLevel, Role,
- Connection:protocol_name(), StateName,
- Alert#alert{role = opposite_role(Role)}),
- Connection:next_event(StateName, no_record, State).
-
-maybe_invalidate_session(undefined,_, _, _, _, _) ->
- ok;
-maybe_invalidate_session({3, 4},_, _, _, _, _) ->
- ok;
-maybe_invalidate_session({3, N}, Type, Role, Host, Port, Session) when N < 4 ->
- maybe_invalidate_session(Type, Role, Host, Port, Session).
-
-%%====================================================================
-%% Data handling
-%%====================================================================
-passive_receive(#state{user_data_buffer = {Front,BufferSize,Rear},
- %% Assert! Erl distribution uses active sockets
- connection_env = #connection_env{erl_dist_handle = undefined}}
- = State0, StateName, Connection, StartTimerAction) ->
- case BufferSize of
- 0 ->
- Connection:next_event(StateName, no_record, State0, StartTimerAction);
- _ ->
- case read_application_data(State0, Front, BufferSize, Rear) of
- {stop, _, _} = ShutdownError ->
- ShutdownError;
- {Record, State} ->
- case State#state.start_or_recv_from of
- undefined ->
- %% Cancel recv timeout as data has been delivered
- Connection:next_event(StateName, Record, State,
- [{{timeout, recv}, infinity, timeout}]);
- _ ->
- Connection:next_event(StateName, Record, State, StartTimerAction)
- end
- end
- end.
-
-read_application_data(
- Data,
- #state{
- user_data_buffer = {Front0,BufferSize0,Rear0},
- connection_env = #connection_env{erl_dist_handle = DHandle}} = State) ->
- %%
- Front = Front0,
- BufferSize = BufferSize0 + byte_size(Data),
- Rear = [Data|Rear0],
- case DHandle of
- undefined ->
- read_application_data(State, Front, BufferSize, Rear);
- _ ->
- try read_application_dist_data(DHandle, Front, BufferSize, Rear) of
- Buffer ->
- {no_record, State#state{user_data_buffer = Buffer}}
- catch error:_ ->
- {stop,disconnect,
- State#state{user_data_buffer = {Front,BufferSize,Rear}}}
- end
- end.
-
-
-read_application_data(#state{
- socket_options = SocketOpts,
- bytes_to_read = BytesToRead,
- start_or_recv_from = RecvFrom} = State, Front, BufferSize, Rear) ->
- read_application_data(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead).
-
-%% Pick binary from queue front, if empty wait for more data
-read_application_data(State, [Bin|Front], BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead) ->
- read_application_data_bin(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead, Bin);
-read_application_data(State, [] = Front, BufferSize, [] = Rear, SocketOpts, RecvFrom, BytesToRead) ->
- 0 = BufferSize, % Assert
- {no_record, State#state{socket_options = SocketOpts,
- bytes_to_read = BytesToRead,
- start_or_recv_from = RecvFrom,
- user_data_buffer = {Front,BufferSize,Rear}}};
-read_application_data(State, [], BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead) ->
- [Bin|Front] = lists:reverse(Rear),
- read_application_data_bin(State, Front, BufferSize, [], SocketOpts, RecvFrom, BytesToRead, Bin).
-
-read_application_data_bin(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead, <<>>) ->
- %% Done with this binary - get next
- read_application_data(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead);
-read_application_data_bin(State, Front0, BufferSize0, Rear0, SocketOpts0, RecvFrom, BytesToRead, Bin0) ->
- %% Decode one packet from a binary
- case get_data(SocketOpts0, BytesToRead, Bin0) of
- {ok, Data, Bin} -> % Send data
- BufferSize = BufferSize0 - (byte_size(Bin0) - byte_size(Bin)),
- read_application_data_deliver(
- State, [Bin|Front0], BufferSize, Rear0, SocketOpts0, RecvFrom, Data);
- {more, undefined} ->
- %% We need more data, do not know how much
- if
- byte_size(Bin0) < BufferSize0 ->
- %% We have more data in the buffer besides the first binary - concatenate all and retry
- Bin = iolist_to_binary([Bin0,Front0|lists:reverse(Rear0)]),
- read_application_data_bin(
- State, [], BufferSize0, [], SocketOpts0, RecvFrom, BytesToRead, Bin);
- true ->
- %% All data is in the first binary, no use to retry - wait for more
- {no_record, State#state{socket_options = SocketOpts0,
- bytes_to_read = BytesToRead,
- start_or_recv_from = RecvFrom,
- user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}}
- end;
- {more, Size} when Size =< BufferSize0 ->
- %% We have a packet in the buffer - collect it in a binary and decode
- {Data,Front,Rear} = iovec_from_front(Size - byte_size(Bin0), Front0, Rear0, [Bin0]),
- Bin = iolist_to_binary(Data),
- read_application_data_bin(
- State, Front, BufferSize0, Rear, SocketOpts0, RecvFrom, BytesToRead, Bin);
- {more, _Size} ->
- %% We do not have a packet in the buffer - wait for more
- {no_record, State#state{socket_options = SocketOpts0,
- bytes_to_read = BytesToRead,
- start_or_recv_from = RecvFrom,
- user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}};
- passive ->
- {no_record, State#state{socket_options = SocketOpts0,
- bytes_to_read = BytesToRead,
- start_or_recv_from = RecvFrom,
- user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}};
- {error,_Reason} ->
- %% Invalid packet in packet mode
- #state{
- static_env =
- #static_env{
- socket = Socket,
- protocol_cb = Connection,
- transport_cb = Transport,
- trackers = Trackers},
- connection_env =
- #connection_env{user_application = {_Mon, Pid}}} = State,
- Buffer = iolist_to_binary([Bin0,Front0|lists:reverse(Rear0)]),
- deliver_packet_error(
- Connection:pids(State), Transport, Socket, SocketOpts0,
- Buffer, Pid, RecvFrom, Trackers, Connection),
- {stop, {shutdown, normal}, State#state{socket_options = SocketOpts0,
- bytes_to_read = BytesToRead,
- start_or_recv_from = RecvFrom,
- user_data_buffer = {[Buffer],BufferSize0,[]}}}
- end.
-
-read_application_data_deliver(State, Front, BufferSize, Rear, SocketOpts0, RecvFrom, Data) ->
- #state{
- static_env =
- #static_env{
- socket = Socket,
- protocol_cb = Connection,
- transport_cb = Transport,
- trackers = Trackers},
- connection_env =
- #connection_env{user_application = {_Mon, Pid}}} = State,
- SocketOpts =
- deliver_app_data(
- Connection:pids(State), Transport, Socket, SocketOpts0, Data, Pid, RecvFrom, Trackers, Connection),
- if
- SocketOpts#socket_options.active =:= false ->
- %% Passive mode, wait for active once or recv
- {no_record,
- State#state{
- user_data_buffer = {Front,BufferSize,Rear},
- start_or_recv_from = undefined,
- bytes_to_read = undefined,
- socket_options = SocketOpts
- }};
- true -> %% Try to deliver more data
- read_application_data(State, Front, BufferSize, Rear, SocketOpts, undefined, undefined)
- end.
-
-
-read_application_dist_data(DHandle, [Bin|Front], BufferSize, Rear) ->
- read_application_dist_data(DHandle, Front, BufferSize, Rear, Bin);
-read_application_dist_data(_DHandle, [] = Front, BufferSize, [] = Rear) ->
- BufferSize = 0,
- {Front,BufferSize,Rear};
-read_application_dist_data(DHandle, [], BufferSize, Rear) ->
- [Bin|Front] = lists:reverse(Rear),
- read_application_dist_data(DHandle, Front, BufferSize, [], Bin).
-%%
-read_application_dist_data(DHandle, Front0, BufferSize, Rear0, Bin0) ->
- case Bin0 of
- %%
- %% START Optimization
- %% It is cheaper to match out several packets in one match operation than to loop for each
- <<SizeA:32, DataA:SizeA/binary,
- SizeB:32, DataB:SizeB/binary,
- SizeC:32, DataC:SizeC/binary,
- SizeD:32, DataD:SizeD/binary, Rest/binary>>
- when 0 < SizeA, 0 < SizeB, 0 < SizeC, 0 < SizeD ->
- %% We have 4 complete packets in the first binary
- erlang:dist_ctrl_put_data(DHandle, DataA),
- erlang:dist_ctrl_put_data(DHandle, DataB),
- erlang:dist_ctrl_put_data(DHandle, DataC),
- erlang:dist_ctrl_put_data(DHandle, DataD),
- read_application_dist_data(
- DHandle, Front0, BufferSize - (4*4+SizeA+SizeB+SizeC+SizeD), Rear0, Rest);
- <<SizeA:32, DataA:SizeA/binary,
- SizeB:32, DataB:SizeB/binary,
- SizeC:32, DataC:SizeC/binary, Rest/binary>>
- when 0 < SizeA, 0 < SizeB, 0 < SizeC ->
- %% We have 3 complete packets in the first binary
- erlang:dist_ctrl_put_data(DHandle, DataA),
- erlang:dist_ctrl_put_data(DHandle, DataB),
- erlang:dist_ctrl_put_data(DHandle, DataC),
- read_application_dist_data(
- DHandle, Front0, BufferSize - (3*4+SizeA+SizeB+SizeC), Rear0, Rest);
- <<SizeA:32, DataA:SizeA/binary,
- SizeB:32, DataB:SizeB/binary, Rest/binary>>
- when 0 < SizeA, 0 < SizeB ->
- %% We have 2 complete packets in the first binary
- erlang:dist_ctrl_put_data(DHandle, DataA),
- erlang:dist_ctrl_put_data(DHandle, DataB),
- read_application_dist_data(
- DHandle, Front0, BufferSize - (2*4+SizeA+SizeB), Rear0, Rest);
- %% END Optimization
- %%
- %% Basic one packet code path
- <<Size:32, Data:Size/binary, Rest/binary>> ->
- %% We have a complete packet in the first binary
- 0 < Size andalso erlang:dist_ctrl_put_data(DHandle, Data),
- read_application_dist_data(DHandle, Front0, BufferSize - (4+Size), Rear0, Rest);
- <<Size:32, FirstData/binary>> when 4+Size =< BufferSize ->
- %% We have a complete packet in the buffer
- %% - fetch the missing content from the buffer front
- {Data,Front,Rear} = iovec_from_front(Size - byte_size(FirstData), Front0, Rear0, [FirstData]),
- 0 < Size andalso erlang:dist_ctrl_put_data(DHandle, Data),
- read_application_dist_data(DHandle, Front, BufferSize - (4+Size), Rear);
- <<Bin/binary>> ->
- %% In OTP-21 the match context reuse optimization fails if we use Bin0 in recursion, so here we
- %% match out the whole binary which will trick the optimization into keeping the match context
- %% for the first binary contains complete packet code above
- case Bin of
- <<_Size:32, _InsufficientData/binary>> ->
- %% We have a length field in the first binary but there is not enough data
- %% in the buffer to form a complete packet - await more data
- {[Bin|Front0],BufferSize,Rear0};
- <<IncompleteLengthField/binary>> when 4 < BufferSize ->
- %% We do not have a length field in the first binary but the buffer
- %% contains enough data to maybe form a packet
- %% - fetch a tiny binary from the buffer front to complete the length field
- {LengthField,Front,Rear} =
- case IncompleteLengthField of
- <<>> ->
- iovec_from_front(4, Front0, Rear0, []);
- _ ->
- iovec_from_front(
- 4 - byte_size(IncompleteLengthField), Front0, Rear0, [IncompleteLengthField])
- end,
- LengthBin = iolist_to_binary(LengthField),
- read_application_dist_data(DHandle, Front, BufferSize, Rear, LengthBin);
- <<IncompleteLengthField/binary>> ->
- %% We do not have enough data in the buffer to even form a length field - await more data
- case IncompleteLengthField of
- <<>> ->
- {Front0,BufferSize,Rear0};
- _ ->
- {[IncompleteLengthField|Front0],BufferSize,Rear0}
- end
- end
- end.
-
-iovec_from_front(0, Front, Rear, Acc) ->
- {lists:reverse(Acc),Front,Rear};
-iovec_from_front(Size, [], Rear, Acc) ->
- case Rear of
- %% Avoid lists:reverse/1 for simple cases.
- %% Case clause for [] to avoid infinite loop.
- [_] ->
- iovec_from_front(Size, Rear, [], Acc);
- [Bin2,Bin1] ->
- iovec_from_front(Size, [Bin1,Bin2], [], Acc);
- [Bin3,Bin2,Bin1] ->
- iovec_from_front(Size, [Bin1,Bin2,Bin3], [], Acc);
- [_,_,_|_] = Rear ->
- iovec_from_front(Size, lists:reverse(Rear), [], Acc)
- end;
-iovec_from_front(Size, [Bin|Front], Rear, []) ->
- case Bin of
- <<Last:Size/binary>> -> % Just enough
- {[Last],Front,Rear};
- <<Last:Size/binary, Rest/binary>> -> % More than enough, split here
- {[Last],[Rest|Front],Rear};
- <<>> -> % Not enough, skip empty binaries
- iovec_from_front(Size, Front, Rear, []);
- <<_/binary>> -> % Not enough
- BinSize = byte_size(Bin),
- iovec_from_front(Size - BinSize, Front, Rear, [Bin])
- end;
-iovec_from_front(Size, [Bin|Front], Rear, Acc) ->
- case Bin of
- <<Last:Size/binary>> -> % Just enough
- {lists:reverse(Acc, [Last]),Front,Rear};
- <<Last:Size/binary, Rest/binary>> -> % More than enough, split here
- {lists:reverse(Acc, [Last]),[Rest|Front],Rear};
- <<>> -> % Not enough, skip empty binaries
- iovec_from_front(Size, Front, Rear, Acc);
- <<_/binary>> -> % Not enough
- BinSize = byte_size(Bin),
- iovec_from_front(Size - BinSize, Front, Rear, [Bin|Acc])
- end.
-
-
-%%====================================================================
-%% Help functions for tls|dtls_connection.erl
-%%====================================================================
-%%--------------------------------------------------------------------
--spec handle_session(#server_hello{}, ssl_record:ssl_version(),
- binary(), ssl_record:connection_states(), _,_, #state{}) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-handle_session(#server_hello{cipher_suite = CipherSuite,
- compression_method = Compression},
- Version, NewId, ConnectionStates, ProtoExt, Protocol0,
- #state{session = #session{session_id = OldId},
- handshake_env = #handshake_env{negotiated_protocol = CurrentProtocol} = HsEnv,
- connection_env = #connection_env{negotiated_version = ReqVersion} = CEnv} = State0) ->
- #{key_exchange := KeyAlgorithm} =
- ssl_cipher_format:suite_bin_to_map(CipherSuite),
-
- PremasterSecret = make_premaster_secret(ReqVersion, KeyAlgorithm),
-
- {ExpectNPN, Protocol} = case Protocol0 of
- undefined ->
-
- {false, CurrentProtocol};
- _ ->
- {ProtoExt =:= npn, Protocol0}
- end,
-
- State = State0#state{connection_states = ConnectionStates,
- handshake_env = HsEnv#handshake_env{kex_algorithm = KeyAlgorithm,
- premaster_secret = PremasterSecret,
- expecting_next_protocol_negotiation = ExpectNPN,
- negotiated_protocol = Protocol},
- connection_env = CEnv#connection_env{negotiated_version = Version}},
-
- case ssl_session:is_new(OldId, NewId) of
- true ->
- handle_new_session(NewId, CipherSuite, Compression,
- State#state{connection_states = ConnectionStates});
- false ->
- handle_resumed_session(NewId,
- State#state{connection_states = ConnectionStates})
- end.
-
-%%--------------------------------------------------------------------
--spec ssl_config(ssl_options(), client | server, #state{}) -> #state{}.
-%%--------------------------------------------------------------------
-ssl_config(Opts, Role, #state{static_env = InitStatEnv0,
- handshake_env = HsEnv,
- connection_env = CEnv} = State0) ->
- {ok, #{cert_db_ref := Ref,
- cert_db_handle := CertDbHandle,
- fileref_db_handle := FileRefHandle,
- session_cache := CacheHandle,
- crl_db_info := CRLDbHandle,
- private_key := Key,
- dh_params := DHParams,
- own_certificate := OwnCert}} =
- ssl_config:init(Opts, Role),
- TimeStamp = erlang:monotonic_time(),
- Session = State0#state.session,
-
- State0#state{session = Session#session{own_certificate = OwnCert,
- time_stamp = TimeStamp},
- static_env = InitStatEnv0#static_env{
- file_ref_db = FileRefHandle,
- cert_db_ref = Ref,
- cert_db = CertDbHandle,
- crl_db = CRLDbHandle,
- session_cache = CacheHandle
- },
- handshake_env = HsEnv#handshake_env{diffie_hellman_params = DHParams},
- connection_env = CEnv#connection_env{private_key = Key},
- ssl_options = Opts}.
-
-%%====================================================================
-%% gen_statem general state functions with connection cb argument
-%%====================================================================
-%%--------------------------------------------------------------------
--spec init(gen_statem:event_type(),
- {start, timeout()} | {start, {list(), list()}, timeout()}| term(),
- #state{}, tls_connection | dtls_connection) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-
-init({call, From}, {start, Timeout}, State0, Connection) ->
- Connection:next_event(hello, no_record, State0#state{start_or_recv_from = From},
- [{{timeout, handshake}, Timeout, close}]);
-init({call, From}, {start, {Opts, EmOpts}, Timeout},
- #state{static_env = #static_env{role = Role},
- ssl_options = OrigSSLOptions,
- socket_options = SockOpts} = State0, Connection) ->
- try
- SslOpts = ssl:handle_options(Opts, Role, OrigSSLOptions),
- State = ssl_config(SslOpts, Role, State0),
- init({call, From}, {start, Timeout},
- State#state{ssl_options = SslOpts,
- socket_options = new_emulated(EmOpts, SockOpts)}, Connection)
- catch throw:Error ->
- {stop_and_reply, {shutdown, normal}, {reply, From, {error, Error}}, State0}
- end;
-init({call, From}, {new_user, _} = Msg, State, Connection) ->
- handle_call(Msg, From, ?FUNCTION_NAME, State, Connection);
-init({call, From}, _Msg, _State, _Connection) ->
- {keep_state_and_data, [{reply, From, {error, notsup_on_transport_accept_socket}}]};
-init(_Type, _Event, _State, _Connection) ->
- {keep_state_and_data, [postpone]}.
-
-%%--------------------------------------------------------------------
--spec error(gen_statem:event_type(),
- {start, timeout()} | term(), #state{},
- tls_connection | dtls_connection) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-error({call, From}, {close, _}, State, _Connection) ->
- {stop_and_reply, {shutdown, normal}, {reply, From, ok}, State};
-error({call, From}, _Msg, State, _Connection) ->
- {next_state, ?FUNCTION_NAME, State, [{reply, From, {error, closed}}]}.
-
-%%--------------------------------------------------------------------
--spec hello(gen_statem:event_type(),
- #hello_request{} | #server_hello{} | term(),
- #state{}, tls_connection | dtls_connection) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-hello({call, From}, Msg, State, Connection) ->
- handle_call(Msg, From, ?FUNCTION_NAME, State, Connection);
-hello(internal, {common_client_hello, Type, ServerHelloExt}, State, Connection) ->
- do_server_hello(Type, ServerHelloExt, State, Connection);
-hello(info, Msg, State, _) ->
- handle_info(Msg, ?FUNCTION_NAME, State);
-hello(Type, Msg, State, Connection) ->
- handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
-
-user_hello({call, From}, cancel, #state{connection_env = #connection_env{negotiated_version = Version}} = State, _) ->
- gen_statem:reply(From, ok),
- handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled),
- Version, ?FUNCTION_NAME, State);
-user_hello({call, From}, {handshake_continue, NewOptions, Timeout},
- #state{static_env = #static_env{role = Role},
- handshake_env = #handshake_env{hello = Hello},
- ssl_options = Options0} = State0, _Connection) ->
- Options = ssl:handle_options(NewOptions, Role, Options0#{handshake => full}),
- State = ssl_config(Options, Role, State0),
- {next_state, hello, State#state{start_or_recv_from = From},
- [{next_event, internal, Hello}, {{timeout, handshake}, Timeout, close}]};
-user_hello(_, _, _, _) ->
- {keep_state_and_data, [postpone]}.
-
-%%--------------------------------------------------------------------
--spec abbreviated(gen_statem:event_type(),
- #hello_request{} | #finished{} | term(),
- #state{}, tls_connection | dtls_connection) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-abbreviated({call, From}, Msg, State, Connection) ->
- handle_call(Msg, From, ?FUNCTION_NAME, State, Connection);
-abbreviated(internal, #finished{verify_data = Data} = Finished,
- #state{static_env = #static_env{role = server},
- handshake_env = #handshake_env{tls_handshake_history = Hist,
- expecting_finished = true} = HsEnv,
- connection_env = #connection_env{negotiated_version = Version},
- session = #session{master_secret = MasterSecret},
- connection_states = ConnectionStates0} =
- State0, Connection) ->
- case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, client,
- get_current_prf(ConnectionStates0, write),
- MasterSecret, Hist) of
- verified ->
- ConnectionStates =
- ssl_record:set_client_verify_data(current_both, Data, ConnectionStates0),
- {Record, State} = prepare_connection(State0#state{connection_states = ConnectionStates,
- handshake_env = HsEnv#handshake_env{expecting_finished = false}}, Connection),
- Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close}]);
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
- end;
-abbreviated(internal, #finished{verify_data = Data} = Finished,
- #state{static_env = #static_env{role = client},
- handshake_env = #handshake_env{tls_handshake_history = Hist0},
- connection_env = #connection_env{negotiated_version = Version},
- session = #session{master_secret = MasterSecret},
- connection_states = ConnectionStates0} = State0, Connection) ->
- case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, server,
- get_pending_prf(ConnectionStates0, write),
- MasterSecret, Hist0) of
- verified ->
- ConnectionStates1 =
- ssl_record:set_server_verify_data(current_read, Data, ConnectionStates0),
- {#state{handshake_env = HsEnv} = State1, Actions} =
- finalize_handshake(State0#state{connection_states = ConnectionStates1},
- ?FUNCTION_NAME, Connection),
- {Record, State} = prepare_connection(State1#state{handshake_env = HsEnv#handshake_env{expecting_finished = false}}, Connection),
- Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close} | Actions]);
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
- end;
-%% only allowed to send next_protocol message after change cipher spec
-%% & before finished message and it is not allowed during renegotiation
-abbreviated(internal, #next_protocol{selected_protocol = SelectedProtocol},
- #state{static_env = #static_env{role = server},
- handshake_env = #handshake_env{expecting_next_protocol_negotiation = true} = HsEnv} = State,
- Connection) ->
- Connection:next_event(?FUNCTION_NAME, no_record,
- State#state{handshake_env = HsEnv#handshake_env{negotiated_protocol = SelectedProtocol,
- expecting_next_protocol_negotiation = false}});
-abbreviated(internal,
- #change_cipher_spec{type = <<1>>},
- #state{connection_states = ConnectionStates0,
- handshake_env = HsEnv} = State, Connection) ->
- ConnectionStates1 =
- ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection),
- Connection:next_event(?FUNCTION_NAME, no_record, State#state{connection_states =
- ConnectionStates1,
- handshake_env = HsEnv#handshake_env{expecting_finished = true}});
-abbreviated(info, Msg, State, _) ->
- handle_info(Msg, ?FUNCTION_NAME, State);
-abbreviated(Type, Msg, State, Connection) ->
- handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
-
-%%--------------------------------------------------------------------
--spec wait_ocsp_stapling(gen_statem:event_type(),
- #certificate{} | #certificate_status{} | term(),
- #state{}, tls_connection | dtls_connection) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-wait_ocsp_stapling(internal, #certificate{}, State, Connection) ->
- %% Postpone message, should be handled in certify after receiving staple message
- Connection:next_event(?FUNCTION_NAME, no_record, State, [{postpone, true}]);
-%% Receive OCSP staple message
-wait_ocsp_stapling(internal, #certificate_status{} = CertStatus,
- #state{handshake_env = #handshake_env{
- ocsp_stapling_state = OcspState} = HsEnv} = State,
- Connection) ->
- Connection:next_event(certify, no_record, State#state{handshake_env = HsEnv#handshake_env{ocsp_stapling_state =
- OcspState#{ocsp_expect => stapled,
- ocsp_response => CertStatus}}});
-%% Server did not send OCSP staple message
-wait_ocsp_stapling(internal, Msg, #state{handshake_env = #handshake_env{
- ocsp_stapling_state = OcspState} = HsEnv} = State, Connection)
- when is_record(Msg, server_key_exchange) orelse
- is_record(Msg, hello_request) orelse
- is_record(Msg, certificate_request) orelse
- is_record(Msg, server_hello_done) orelse
- is_record(Msg, client_key_exchange) ->
- Connection:next_event(certify, no_record, State#state{handshake_env =
- HsEnv#handshake_env{ocsp_stapling_state = OcspState#{ocsp_expect => undetermined}}},
- [{postpone, true}]);
-wait_ocsp_stapling(Type, Msg, State, Connection) ->
- handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
-
-%%--------------------------------------------------------------------
--spec certify(gen_statem:event_type(),
- #hello_request{} | #certificate{} | #server_key_exchange{} |
- #certificate_request{} | #server_hello_done{} | #client_key_exchange{} | term(),
- #state{}, tls_connection | dtls_connection) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-certify({call, From}, Msg, State, Connection) ->
- handle_call(Msg, From, ?FUNCTION_NAME, State, Connection);
-certify(info, Msg, State, _) ->
- handle_info(Msg, ?FUNCTION_NAME, State);
-certify(internal, #certificate{asn1_certificates = []},
- #state{static_env = #static_env{role = server},
- connection_env = #connection_env{negotiated_version = Version},
- ssl_options = #{verify := verify_peer,
- fail_if_no_peer_cert := true}} =
- State, _) ->
- Alert = ?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, no_client_certificate_provided),
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State);
-certify(internal, #certificate{asn1_certificates = []},
- #state{static_env = #static_env{role = server},
- ssl_options = #{verify := verify_peer,
- fail_if_no_peer_cert := false}} =
- State0, Connection) ->
- Connection:next_event(?FUNCTION_NAME, no_record, State0#state{client_certificate_requested = false});
-certify(internal, #certificate{},
- #state{static_env = #static_env{role = server},
- connection_env = #connection_env{negotiated_version = Version},
- ssl_options = #{verify := verify_none}} =
- State, _) ->
- Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, unrequested_certificate),
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State);
-certify(internal, #certificate{},
- #state{handshake_env = #handshake_env{
- ocsp_stapling_state = #{ocsp_expect := staple}}} = State, Connection) ->
- Connection:next_event(wait_ocsp_stapling, no_record, State, [{postpone, true}]);
-certify(internal, #certificate{asn1_certificates = [Peer|_]} = Cert, #state{static_env =
- #static_env{
- role = Role,
- host = Host,
- cert_db = CertDbHandle,
- cert_db_ref = CertDbRef,
- crl_db = CRLDbInfo},
- handshake_env = #handshake_env{
- ocsp_stapling_state = #{ocsp_expect := Status} = OcspState},
- connection_env = #connection_env{
- negotiated_version = Version},
- ssl_options = Opts} = State, Connection) when Status =/= staple ->
- OcspInfo = ocsp_info(OcspState, Opts, Peer),
- case ssl_handshake:certify(Cert, CertDbHandle, CertDbRef,
- Opts, CRLDbInfo, Role, Host,
- ensure_tls(Version), OcspInfo) of
- {PeerCert, PublicKeyInfo} ->
- handle_peer_cert(Role, PeerCert, PublicKeyInfo,
- State#state{client_certificate_requested = false}, Connection, []);
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)
- end;
-certify(internal, #server_key_exchange{exchange_keys = Keys},
- #state{static_env = #static_env{role = client},
- handshake_env = #handshake_env{kex_algorithm = KexAlg,
- public_key_info = PubKeyInfo} = HsEnv,
- connection_env = #connection_env{negotiated_version = Version},
- session = Session,
- connection_states = ConnectionStates} = State, Connection)
- when KexAlg == dhe_dss;
- KexAlg == dhe_rsa;
- KexAlg == ecdhe_rsa;
- KexAlg == ecdhe_ecdsa;
- KexAlg == dh_anon;
- KexAlg == ecdh_anon;
- KexAlg == psk;
- KexAlg == dhe_psk;
- KexAlg == ecdhe_psk;
- KexAlg == rsa_psk;
- KexAlg == srp_dss;
- KexAlg == srp_rsa;
- KexAlg == srp_anon ->
-
- Params = ssl_handshake:decode_server_key(Keys, KexAlg, ssl:tls_version(Version)),
-
- %% Use negotiated value if TLS-1.2 otherwhise return default
- HashSign = negotiated_hashsign(Params#server_key_params.hashsign, KexAlg, PubKeyInfo, ssl:tls_version(Version)),
-
- case is_anonymous(KexAlg) of
- true ->
- calculate_secret(Params#server_key_params.params,
- State#state{handshake_env = HsEnv#handshake_env{hashsign_algorithm = HashSign}}, Connection);
- false ->
- case ssl_handshake:verify_server_key(Params, HashSign,
- ConnectionStates, ssl:tls_version(Version), PubKeyInfo) of
- true ->
- calculate_secret(Params#server_key_params.params,
- State#state{handshake_env = HsEnv#handshake_env{hashsign_algorithm = HashSign},
- session = session_handle_params(Params#server_key_params.params, Session)},
- Connection);
- false ->
- handle_own_alert(?ALERT_REC(?FATAL, ?DECRYPT_ERROR),
- Version, ?FUNCTION_NAME, State)
- end
- end;
-certify(internal, #certificate_request{},
- #state{static_env = #static_env{role = client},
- handshake_env = #handshake_env{kex_algorithm = KexAlg},
- connection_env = #connection_env{negotiated_version = Version}} = State, _)
- when KexAlg == dh_anon;
- KexAlg == ecdh_anon;
- KexAlg == psk;
- KexAlg == dhe_psk;
- KexAlg == ecdhe_psk;
- KexAlg == rsa_psk;
- KexAlg == srp_dss;
- KexAlg == srp_rsa;
- KexAlg == srp_anon ->
- handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE),
- Version, ?FUNCTION_NAME, State);
-certify(internal, #certificate_request{},
- #state{static_env = #static_env{role = client},
- session = #session{own_certificate = undefined}} = State, Connection) ->
- %% The client does not have a certificate and will send an empty reply, the server may fail
- %% or accept the connection by its own preference. No signature algorihms needed as there is
- %% no certificate to verify.
- Connection:next_event(?FUNCTION_NAME, no_record, State#state{client_certificate_requested = true});
-certify(internal, #certificate_request{} = CertRequest,
- #state{static_env = #static_env{role = client},
- handshake_env = HsEnv,
- connection_env = #connection_env{negotiated_version = Version},
- session = #session{own_certificate = Cert},
- ssl_options = #{signature_algs := SupportedHashSigns}} = State, Connection) ->
- case ssl_handshake:select_hashsign(CertRequest, Cert,
- SupportedHashSigns, ssl:tls_version(Version)) of
- #alert {} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State);
- NegotiatedHashSign ->
- Connection:next_event(?FUNCTION_NAME, no_record,
- State#state{client_certificate_requested = true,
- handshake_env = HsEnv#handshake_env{cert_hashsign_algorithm = NegotiatedHashSign}})
- end;
-%% PSK and RSA_PSK might bypass the Server-Key-Exchange
-certify(internal, #server_hello_done{},
- #state{static_env = #static_env{role = client},
- session = #session{master_secret = undefined},
- connection_env = #connection_env{negotiated_version = Version},
- handshake_env = #handshake_env{kex_algorithm = KexAlg,
- premaster_secret = undefined,
- server_psk_identity = PSKIdentity} = HsEnv,
- ssl_options = #{user_lookup_fun := PSKLookup}} = State0, Connection)
- when KexAlg == psk ->
- case ssl_handshake:premaster_secret({KexAlg, PSKIdentity}, PSKLookup) of
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0);
- PremasterSecret ->
- State = master_secret(PremasterSecret,
- State0#state{handshake_env =
- HsEnv#handshake_env{premaster_secret = PremasterSecret}}),
- client_certify_and_key_exchange(State, Connection)
- end;
-certify(internal, #server_hello_done{},
- #state{static_env = #static_env{role = client},
- connection_env = #connection_env{negotiated_version = {Major, Minor}} = Version,
- handshake_env = #handshake_env{kex_algorithm = KexAlg,
- premaster_secret = undefined,
- server_psk_identity = PSKIdentity} = HsEnv,
- session = #session{master_secret = undefined},
- ssl_options = #{user_lookup_fun := PSKLookup}} = State0, Connection)
- when KexAlg == rsa_psk ->
- Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2),
- RSAPremasterSecret = <<?BYTE(Major), ?BYTE(Minor), Rand/binary>>,
- case ssl_handshake:premaster_secret({KexAlg, PSKIdentity}, PSKLookup,
- RSAPremasterSecret) of
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0);
- PremasterSecret ->
- State = master_secret(PremasterSecret,
- State0#state{handshake_env =
- HsEnv#handshake_env{premaster_secret = RSAPremasterSecret}}),
- client_certify_and_key_exchange(State, Connection)
- end;
-%% Master secret was determined with help of server-key exchange msg
-certify(internal, #server_hello_done{},
- #state{static_env = #static_env{role = client},
- connection_env = #connection_env{negotiated_version = Version},
- handshake_env = #handshake_env{premaster_secret = undefined},
- session = #session{master_secret = MasterSecret} = Session,
- connection_states = ConnectionStates0} = State0, Connection) ->
- case ssl_handshake:master_secret(ssl:tls_version(Version), Session,
- ConnectionStates0, client) of
- {MasterSecret, ConnectionStates} ->
- State = State0#state{connection_states = ConnectionStates},
- client_certify_and_key_exchange(State, Connection);
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
- end;
-%% Master secret is calculated from premaster_secret
-certify(internal, #server_hello_done{},
- #state{static_env = #static_env{role = client},
- connection_env = #connection_env{negotiated_version = Version},
- handshake_env = #handshake_env{premaster_secret = PremasterSecret},
- session = Session0,
- connection_states = ConnectionStates0} = State0, Connection) ->
- case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret,
- ConnectionStates0, client) of
- {MasterSecret, ConnectionStates} ->
- Session = Session0#session{master_secret = MasterSecret},
- State = State0#state{connection_states = ConnectionStates,
- session = Session},
- client_certify_and_key_exchange(State, Connection);
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
- end;
-certify(internal = Type, #client_key_exchange{} = Msg,
- #state{static_env = #static_env{role = server},
- client_certificate_requested = true,
- ssl_options = #{fail_if_no_peer_cert := true}} = State,
- Connection) ->
- %% We expect a certificate here
- handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection);
-certify(internal, #client_key_exchange{exchange_keys = Keys},
- State = #state{handshake_env = #handshake_env{kex_algorithm = KeyAlg},
- connection_env = #connection_env{negotiated_version = Version}}, Connection) ->
- try
- certify_client_key_exchange(ssl_handshake:decode_client_key(Keys, KeyAlg, ssl:tls_version(Version)),
- State, Connection)
- catch
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)
- end;
-certify(Type, Msg, State, Connection) ->
- handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
-
-%%--------------------------------------------------------------------
--spec cipher(gen_statem:event_type(),
- #hello_request{} | #certificate_verify{} | #finished{} | term(),
- #state{}, tls_connection | dtls_connection) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-cipher({call, From}, Msg, State, Connection) ->
- handle_call(Msg, From, ?FUNCTION_NAME, State, Connection);
-cipher(info, Msg, State, _) ->
- handle_info(Msg, ?FUNCTION_NAME, State);
-cipher(internal, #certificate_verify{signature = Signature,
- hashsign_algorithm = CertHashSign},
- #state{static_env = #static_env{role = server},
- handshake_env = #handshake_env{tls_handshake_history = Hist,
- kex_algorithm = KexAlg,
- public_key_info = PubKeyInfo} = HsEnv,
- connection_env = #connection_env{negotiated_version = Version},
- session = #session{master_secret = MasterSecret}
- } = State, Connection) ->
-
- TLSVersion = ssl:tls_version(Version),
- %% Use negotiated value if TLS-1.2 otherwhise return default
- HashSign = negotiated_hashsign(CertHashSign, KexAlg, PubKeyInfo, TLSVersion),
- case ssl_handshake:certificate_verify(Signature, PubKeyInfo,
- TLSVersion, HashSign, MasterSecret, Hist) of
- valid ->
- Connection:next_event(?FUNCTION_NAME, no_record,
- State#state{handshake_env = HsEnv#handshake_env{cert_hashsign_algorithm = HashSign}});
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)
- end;
-%% client must send a next protocol message if we are expecting it
-cipher(internal, #finished{},
- #state{static_env = #static_env{role = server},
- handshake_env = #handshake_env{expecting_next_protocol_negotiation = true,
- negotiated_protocol = undefined},
- connection_env = #connection_env{negotiated_version = Version}} = State0,
- _Connection) ->
- handle_own_alert(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), Version, ?FUNCTION_NAME, State0);
-cipher(internal, #finished{verify_data = Data} = Finished,
- #state{static_env = #static_env{role = Role,
- host = Host,
- port = Port,
- trackers = Trackers},
- handshake_env = #handshake_env{tls_handshake_history = Hist,
- expecting_finished = true} = HsEnv,
- connection_env = #connection_env{negotiated_version = Version},
- session = #session{master_secret = MasterSecret}
- = Session0,
- ssl_options = SslOpts,
- connection_states = ConnectionStates0} = State, Connection) ->
- case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished,
- opposite_role(Role),
- get_current_prf(ConnectionStates0, read),
- MasterSecret, Hist) of
- verified ->
- Session = handle_session(Role, SslOpts, Host, Port, Trackers, Session0),
- cipher_role(Role, Data, Session,
- State#state{handshake_env = HsEnv#handshake_env{expecting_finished = false}}, Connection);
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)
- end;
-%% only allowed to send next_protocol message after change cipher spec
-%% & before finished message and it is not allowed during renegotiation
-cipher(internal, #next_protocol{selected_protocol = SelectedProtocol},
- #state{static_env = #static_env{role = server},
- handshake_env = #handshake_env{expecting_finished = true,
- expecting_next_protocol_negotiation = true} = HsEnv} = State, Connection) ->
- Connection:next_event(?FUNCTION_NAME, no_record,
- State#state{handshake_env = HsEnv#handshake_env{negotiated_protocol = SelectedProtocol,
- expecting_next_protocol_negotiation = false}});
-cipher(internal, #change_cipher_spec{type = <<1>>}, #state{handshake_env = HsEnv, connection_states = ConnectionStates0} =
- State, Connection) ->
- ConnectionStates =
- ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection),
- Connection:next_event(?FUNCTION_NAME, no_record, State#state{handshake_env = HsEnv#handshake_env{expecting_finished = true},
- connection_states = ConnectionStates});
-cipher(Type, Msg, State, Connection) ->
- handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
-
-%%--------------------------------------------------------------------
--spec connection(gen_statem:event_type(), term(),
- #state{}, tls_connection | dtls_connection) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-connection({call, RecvFrom}, {recv, N, Timeout},
- #state{static_env = #static_env{protocol_cb = Connection},
- socket_options =
- #socket_options{active = false}} = State0, Connection) ->
- passive_receive(State0#state{bytes_to_read = N,
- start_or_recv_from = RecvFrom}, ?FUNCTION_NAME, Connection,
- [{{timeout, recv}, Timeout, timeout}]);
-
-connection({call, From}, renegotiate, #state{static_env = #static_env{protocol_cb = Connection},
- handshake_env = HsEnv} = State,
- Connection) ->
- Connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, From}}}, []);
-connection({call, From}, peer_certificate,
- #state{session = #session{peer_certificate = Cert}} = State, _) ->
- hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Cert}}]);
-connection({call, From}, {connection_information, true}, State, _) ->
- Info = connection_info(State) ++ security_info(State),
- hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Info}}]);
-connection({call, From}, {connection_information, false}, State, _) ->
- Info = connection_info(State),
- hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Info}}]);
-connection({call, From}, negotiated_protocol,
- #state{handshake_env = #handshake_env{alpn = undefined,
- negotiated_protocol = undefined}} = State, _) ->
- hibernate_after(?FUNCTION_NAME, State, [{reply, From, {error, protocol_not_negotiated}}]);
-connection({call, From}, negotiated_protocol,
- #state{handshake_env = #handshake_env{alpn = undefined,
- negotiated_protocol = SelectedProtocol}} = State, _) ->
- hibernate_after(?FUNCTION_NAME, State,
- [{reply, From, {ok, SelectedProtocol}}]);
-connection({call, From}, negotiated_protocol,
- #state{handshake_env = #handshake_env{alpn = SelectedProtocol,
- negotiated_protocol = undefined}} = State, _) ->
- hibernate_after(?FUNCTION_NAME, State,
- [{reply, From, {ok, SelectedProtocol}}]);
-connection({call, From}, Msg, State, Connection) ->
- handle_call(Msg, From, ?FUNCTION_NAME, State, Connection);
-connection(cast, {internal_renegotiate, WriteState}, #state{static_env = #static_env{protocol_cb = Connection},
- handshake_env = HsEnv,
- connection_states = ConnectionStates}
- = State, Connection) ->
- Connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, internal}},
- connection_states = ConnectionStates#{current_write => WriteState}}, []);
-connection(cast, {dist_handshake_complete, DHandle},
- #state{ssl_options = #{erl_dist := true},
- connection_env = CEnv,
- socket_options = SockOpts} = State0, Connection) ->
- process_flag(priority, normal),
- State1 =
- State0#state{
- socket_options = SockOpts#socket_options{active = true},
- connection_env = CEnv#connection_env{erl_dist_handle = DHandle},
- bytes_to_read = undefined},
- {Record, State} = read_application_data(<<>>, State1),
- Connection:next_event(connection, Record, State);
-connection(info, Msg, State, _) ->
- handle_info(Msg, ?FUNCTION_NAME, State);
-connection(internal, {recv, RecvFrom}, #state{start_or_recv_from = RecvFrom} = State, Connection) ->
- passive_receive(State, ?FUNCTION_NAME, Connection, []);
-connection(Type, Msg, State, Connection) ->
- handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
-
-%%--------------------------------------------------------------------
--spec downgrade(gen_statem:event_type(), term(),
- #state{}, tls_connection | dtls_connection) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-downgrade(Type, Event, State, Connection) ->
- handle_common_event(Type, Event, ?FUNCTION_NAME, State, Connection).
-
-%%--------------------------------------------------------------------
-%% Event handling functions called by state functions to handle
-%% common or unexpected events for the state.
-%%--------------------------------------------------------------------
-handle_common_event(internal, {handshake, {#hello_request{} = Handshake, _}}, connection = StateName,
- #state{static_env = #static_env{role = client},
- handshake_env = HsEnv} = State, _) ->
- %% Should not be included in handshake history
- {next_state, StateName, State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, peer}}},
- [{next_event, internal, Handshake}]};
-handle_common_event(internal, {handshake, {#hello_request{}, _}}, StateName,
- #state{static_env = #static_env{role = client}}, _)
- when StateName =/= connection ->
- keep_state_and_data;
-handle_common_event(internal, {handshake, {Handshake, Raw}}, StateName,
- #state{handshake_env = #handshake_env{tls_handshake_history = Hist0},
- connection_env = #connection_env{negotiated_version = Version}} = State0,
- Connection) ->
-
- PossibleSNI = Connection:select_sni_extension(Handshake),
- %% This function handles client SNI hello extension when Handshake is
- %% a client_hello, which needs to be determined by the connection callback.
- %% In other cases this is a noop
- case handle_sni_extension(PossibleSNI, State0) of
- #state{handshake_env = HsEnv} = State ->
- Hist = ssl_handshake:update_handshake_history(Hist0, Raw),
- {next_state, StateName, State#state{handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}},
- [{next_event, internal, Handshake}]};
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, StateName, State0)
- end;
-handle_common_event(internal, {protocol_record, TLSorDTLSRecord}, StateName, State, Connection) ->
- Connection:handle_protocol_record(TLSorDTLSRecord, StateName, State);
-handle_common_event(timeout, hibernate, _, _, _) ->
- {keep_state_and_data, [hibernate]};
-handle_common_event(internal, #change_cipher_spec{type = <<1>>}, StateName,
- #state{connection_env = #connection_env{negotiated_version = Version}} = State, _) ->
- handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), Version,
- StateName, State);
-handle_common_event({timeout, handshake}, close, _StateName, #state{start_or_recv_from = StartFrom} = State, _) ->
- {stop_and_reply,
- {shutdown, user_timeout},
- {reply, StartFrom, {error, timeout}}, State#state{start_or_recv_from = undefined}};
-handle_common_event({timeout, recv}, timeout, StateName, #state{start_or_recv_from = RecvFrom} = State, _) ->
- {next_state, StateName, State#state{start_or_recv_from = undefined,
- bytes_to_read = undefined}, [{reply, RecvFrom, {error, timeout}}]};
-handle_common_event(internal, {recv, RecvFrom}, StateName, #state{start_or_recv_from = RecvFrom}, _) when
- StateName =/= connection ->
- {keep_state_and_data, [postpone]};
-handle_common_event(Type, Msg, StateName, #state{connection_env =
- #connection_env{negotiated_version = Version}} = State,
- _) ->
- Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, {unexpected_msg, {Type,Msg}}),
- handle_own_alert(Alert, Version, StateName, State).
-
-handle_call({application_data, _Data}, _, _, _, _) ->
- %% In renegotiation priorities handshake, send data when handshake is finished
- {keep_state_and_data, [postpone]};
-handle_call({close, _} = Close, From, StateName, #state{connection_env = CEnv} = State, _Connection) ->
- %% Run terminate before returning so that the reuseaddr
- %% inet-option works properly
- Result = terminate(Close, StateName, State),
- {stop_and_reply,
- {shutdown, normal},
- {reply, From, Result}, State#state{connection_env = CEnv#connection_env{terminated = true}}};
-handle_call({shutdown, read_write = How}, From, StateName,
- #state{static_env = #static_env{transport_cb = Transport,
- socket = Socket},
- connection_env = CEnv} = State, _) ->
- try send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
- StateName, State) of
- _ ->
- case Transport:shutdown(Socket, How) of
- ok ->
- {next_state, StateName, State#state{connection_env =
- CEnv#connection_env{terminated = true}},
- [{reply, From, ok}]};
- Error ->
- {stop_and_reply, {shutdown, normal}, {reply, From, Error},
- State#state{connection_env = CEnv#connection_env{terminated = true}}}
- end
- catch
- throw:Return ->
- Return
- end;
-handle_call({shutdown, How0}, From, StateName,
- #state{static_env = #static_env{transport_cb = Transport,
- socket = Socket}} = State, _) ->
- case Transport:shutdown(Socket, How0) of
- ok ->
- {next_state, StateName, State, [{reply, From, ok}]};
- Error ->
- {stop_and_reply, {shutdown, normal}, {reply, From, Error}, State}
- end;
-handle_call({recv, _N, _Timeout}, From, _,
- #state{socket_options =
- #socket_options{active = Active}}, _) when Active =/= false ->
- {keep_state_and_data, [{reply, From, {error, einval}}]};
-handle_call({recv, N, Timeout}, RecvFrom, StateName, State, _) ->
- %% Doing renegotiate wait with handling request until renegotiate is
- %% finished.
- {next_state, StateName, State#state{bytes_to_read = N, start_or_recv_from = RecvFrom},
- [{next_event, internal, {recv, RecvFrom}} , {{timeout, recv}, Timeout, timeout}]};
-handle_call({new_user, User}, From, StateName,
- State = #state{connection_env = #connection_env{user_application = {OldMon, _}} = CEnv}, _) ->
- NewMon = erlang:monitor(process, User),
- erlang:demonitor(OldMon, [flush]),
- {next_state, StateName, State#state{connection_env = CEnv#connection_env{user_application = {NewMon, User}}},
- [{reply, From, ok}]};
-handle_call({get_opts, OptTags}, From, _,
- #state{static_env = #static_env{socket = Socket,
- transport_cb = Transport},
- socket_options = SockOpts}, Connection) ->
- OptsReply = get_socket_opts(Connection, Transport, Socket, OptTags, SockOpts, []),
- {keep_state_and_data, [{reply, From, OptsReply}]};
-handle_call({set_opts, Opts0}, From, StateName,
- #state{static_env = #static_env{socket = Socket,
- transport_cb = Transport,
- trackers = Trackers},
- connection_env =
- #connection_env{user_application = {_Mon, Pid}},
- socket_options = Opts1
- } = State0, Connection) ->
- {Reply, Opts} = set_socket_opts(Connection, Transport, Socket, Opts0, Opts1, []),
- case {proplists:lookup(active, Opts0), Opts} of
- {{_, N}, #socket_options{active=false}} when is_integer(N) ->
- send_user(
- Pid,
- format_passive(
- Connection:pids(State0), Transport, Socket, Trackers, Connection));
- _ ->
- ok
- end,
- State = State0#state{socket_options = Opts},
- handle_active_option(Opts#socket_options.active, StateName, From, Reply, State);
-
-handle_call(renegotiate, From, StateName, _, _) when StateName =/= connection ->
- {keep_state_and_data, [{reply, From, {error, already_renegotiating}}]};
-
-handle_call({prf, Secret, Label, Seed, WantedLength}, From, _,
- #state{connection_states = ConnectionStates,
- connection_env = #connection_env{negotiated_version = Version}}, _) ->
- #{security_parameters := SecParams} =
- ssl_record:current_connection_state(ConnectionStates, read),
- #security_parameters{master_secret = MasterSecret,
- client_random = ClientRandom,
- server_random = ServerRandom,
- prf_algorithm = PRFAlgorithm} = SecParams,
- Reply = try
- SecretToUse = case Secret of
- _ when is_binary(Secret) -> Secret;
- master_secret -> MasterSecret
- end,
- SeedToUse = lists:reverse(
- lists:foldl(fun(X, Acc) when is_binary(X) -> [X|Acc];
- (client_random, Acc) -> [ClientRandom|Acc];
- (server_random, Acc) -> [ServerRandom|Acc]
- end, [], Seed)),
- ssl_handshake:prf(ssl:tls_version(Version), PRFAlgorithm, SecretToUse, Label, SeedToUse, WantedLength)
- catch
- exit:_ -> {error, badarg};
- error:Reason -> {error, Reason}
- end,
- {keep_state_and_data, [{reply, From, Reply}]};
-handle_call(_,_,_,_,_) ->
- {keep_state_and_data, [postpone]}.
-
-handle_info({ErrorTag, Socket, econnaborted}, StateName,
- #state{static_env = #static_env{role = Role,
- host = Host,
- port = Port,
- socket = Socket,
- transport_cb = Transport,
- error_tag = ErrorTag,
- trackers = Trackers,
- protocol_cb = Connection},
- handshake_env = #handshake_env{renegotiation = Type},
- connection_env = #connection_env{negotiated_version = Version},
- session = Session,
- start_or_recv_from = StartFrom
- } = State) when StateName =/= connection ->
-
- maybe_invalidate_session(Version, Type, Role, Host, Port, Session),
- Pids = Connection:pids(State),
- alert_user(Pids, Transport, Trackers,Socket,
- StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role, StateName, Connection),
- {stop, {shutdown, normal}, State};
-
-handle_info({ErrorTag, Socket, Reason}, StateName, #state{static_env = #static_env{
- role = Role,
- socket = Socket,
- error_tag = ErrorTag},
- ssl_options = #{log_level := Level}} = State) ->
- ssl_logger:log(info, Level, #{description => "Socket error",
- reason => [{error_tag, ErrorTag}, {description, Reason}]}, ?LOCATION),
- Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, {transport_error, Reason}),
- handle_normal_shutdown(Alert#alert{role = Role}, StateName, State),
- {stop, {shutdown,normal}, State};
-
-handle_info({'DOWN', MonitorRef, _, _, Reason}, _,
- #state{connection_env = #connection_env{user_application = {MonitorRef, _Pid}},
- ssl_options = #{erl_dist := true}}) ->
- {stop, {shutdown, Reason}};
-handle_info({'DOWN', MonitorRef, _, _, _}, _,
- #state{connection_env = #connection_env{user_application = {MonitorRef, _Pid}}}) ->
- {stop, {shutdown, normal}};
-handle_info({'EXIT', Pid, _Reason}, StateName,
- #state{connection_env = #connection_env{user_application = {_MonitorRef, Pid}}} = State) ->
- %% It seems the user application has linked to us
- %% - ignore that and let the monitor handle this
- {next_state, StateName, State};
-%%% So that terminate will be run when supervisor issues shutdown
-handle_info({'EXIT', _Sup, shutdown}, _StateName, State) ->
- {stop, shutdown, State};
-handle_info({'EXIT', Socket, normal}, _StateName, #state{static_env = #static_env{socket = Socket}} = State) ->
- %% Handle as transport close"
- {stop,{shutdown, transport_closed}, State};
-handle_info({'EXIT', Socket, Reason}, _StateName, #state{static_env = #static_env{socket = Socket}} = State) ->
- {stop,{shutdown, Reason}, State};
-
-handle_info(allow_renegotiate, StateName, #state{handshake_env = HsEnv} = State) ->
- {next_state, StateName, State#state{handshake_env = HsEnv#handshake_env{allow_renegotiate = true}}};
-
-handle_info(Msg, StateName, #state{static_env = #static_env{socket = Socket, error_tag = ErrorTag},
- ssl_options = #{log_level := Level}} = State) ->
- ssl_logger:log(notice, Level, #{description => "Unexpected INFO message",
- reason => [{message, Msg}, {socket, Socket},
- {error_tag, ErrorTag}]}, ?LOCATION),
- {next_state, StateName, State}.
-
-%%====================================================================
-%% general gen_statem callbacks
-%%====================================================================
-terminate(_, _, #state{connection_env = #connection_env{terminated = true}}) ->
- %% Happens when user closes the connection using ssl:close/1
- %% we want to guarantee that Transport:close has been called
- %% when ssl:close/1 returns unless it is a downgrade where
- %% we want to guarantee that close alert is received before
- %% returning. In both cases terminate has been run manually
- %% before run by gen_statem which will end up here
- ok;
-terminate({shutdown, transport_closed} = Reason,
- _StateName, #state{static_env = #static_env{protocol_cb = Connection,
- socket = Socket,
- transport_cb = Transport}} = State) ->
- handle_trusted_certs_db(State),
- Connection:close(Reason, Socket, Transport, undefined, undefined);
-terminate({shutdown, own_alert}, _StateName, #state{
- static_env = #static_env{protocol_cb = Connection,
- socket = Socket,
- transport_cb = Transport}} = State) ->
- handle_trusted_certs_db(State),
- case application:get_env(ssl, alert_timeout) of
- {ok, Timeout} when is_integer(Timeout) ->
- Connection:close({timeout, Timeout}, Socket, Transport, undefined, undefined);
- _ ->
- Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, undefined, undefined)
- end;
-terminate({shutdown, downgrade = Reason}, downgrade, #state{static_env = #static_env{protocol_cb = Connection,
- transport_cb = Transport,
- socket = Socket}
- } = State) ->
- handle_trusted_certs_db(State),
- Connection:close(Reason, Socket, Transport, undefined, undefined);
-terminate(Reason, connection, #state{static_env = #static_env{
- protocol_cb = Connection,
- transport_cb = Transport,
- socket = Socket},
- connection_states = ConnectionStates,
- ssl_options = #{padding_check := Check}
- } = State) ->
- handle_trusted_certs_db(State),
- Alert = terminate_alert(Reason),
- %% Send the termination ALERT if possible
- catch (ok = Connection:send_alert_in_connection(Alert, State)),
- Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, ConnectionStates, Check);
-terminate(Reason, _StateName, #state{static_env = #static_env{transport_cb = Transport,
- protocol_cb = Connection,
- socket = Socket}
- } = State) ->
- handle_trusted_certs_db(State),
- Connection:close(Reason, Socket, Transport, undefined, undefined).
-
-format_status(normal, [_, StateName, State]) ->
- [{data, [{"State", {StateName, State}}]}];
-format_status(terminate, [_, StateName, State]) ->
- SslOptions = (State#state.ssl_options),
- NewOptions = SslOptions#{password => ?SECRET_PRINTOUT,
- cert => ?SECRET_PRINTOUT,
- cacerts => ?SECRET_PRINTOUT,
- key => ?SECRET_PRINTOUT,
- dh => ?SECRET_PRINTOUT,
- psk_identity => ?SECRET_PRINTOUT,
- srp_identity => ?SECRET_PRINTOUT},
- [{data, [{"State", {StateName, State#state{connection_states = ?SECRET_PRINTOUT,
- protocol_buffers = ?SECRET_PRINTOUT,
- user_data_buffer = ?SECRET_PRINTOUT,
- handshake_env = ?SECRET_PRINTOUT,
- connection_env = ?SECRET_PRINTOUT,
- session = ?SECRET_PRINTOUT,
- ssl_options = NewOptions,
- flight_buffer = ?SECRET_PRINTOUT}
- }}]}].
-
-%%--------------------------------------------------------------------
-%%% Internal functions
-%%--------------------------------------------------------------------
-send_alert(Alert, connection, #state{static_env = #static_env{protocol_cb = Connection}} = State) ->
- Connection:send_alert_in_connection(Alert, State);
-send_alert(Alert, _, #state{static_env = #static_env{protocol_cb = Connection}} = State) ->
- Connection:send_alert(Alert, State).
-
-connection_info(#state{static_env = #static_env{protocol_cb = Connection},
- handshake_env = #handshake_env{sni_hostname = SNIHostname,
- resumption = Resumption},
- session = #session{session_id = SessionId,
- cipher_suite = CipherSuite,
- srp_username = SrpUsername,
- ecc = ECCCurve},
- connection_states = #{current_write := CurrentWrite},
- connection_env = #connection_env{negotiated_version = {_,_} = Version},
- ssl_options = Opts}) ->
- RecordCB = record_cb(Connection),
- CipherSuiteDef = #{key_exchange := KexAlg} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
- IsNamedCurveSuite = lists:member(KexAlg,
- [ecdh_ecdsa, ecdhe_ecdsa, ecdh_rsa, ecdhe_rsa, ecdh_anon]),
- CurveInfo = case ECCCurve of
- {namedCurve, Curve} when IsNamedCurveSuite ->
- [{ecc, {named_curve, pubkey_cert_records:namedCurves(Curve)}}];
- _ ->
- []
- end,
-
- MFLInfo = case maps:get(max_fragment_length, CurrentWrite, undefined) of
- MaxFragmentLength when is_integer(MaxFragmentLength) ->
- [{max_fragment_length, MaxFragmentLength}];
- _ ->
- []
- end,
- [{protocol, RecordCB:protocol_version(Version)},
- {session_id, SessionId},
- {session_resumption, Resumption},
- {selected_cipher_suite, CipherSuiteDef},
- {sni_hostname, SNIHostname},
- {srp_username, SrpUsername} | CurveInfo] ++ MFLInfo ++ ssl_options_list(Opts).
-
-security_info(#state{connection_states = ConnectionStates}) ->
- #{security_parameters :=
- #security_parameters{client_random = ClientRand,
- server_random = ServerRand,
- master_secret = MasterSecret}} =
- ssl_record:current_connection_state(ConnectionStates, read),
- [{client_random, ClientRand}, {server_random, ServerRand}, {master_secret, MasterSecret}].
-
-do_server_hello(Type, #{next_protocol_negotiation := NextProtocols} =
- ServerHelloExt,
- #state{connection_env = #connection_env{negotiated_version = Version},
- handshake_env = HsEnv,
- session = #session{session_id = SessId},
- connection_states = ConnectionStates0,
- ssl_options = #{versions := [HighestVersion|_]}}
- = State0, Connection) when is_atom(Type) ->
- %% TLS 1.3 - Section 4.1.3
- %% Override server random values for TLS 1.3 downgrade protection mechanism.
- ConnectionStates1 = update_server_random(ConnectionStates0, Version, HighestVersion),
- State1 = State0#state{connection_states = ConnectionStates1},
- ServerHello =
- ssl_handshake:server_hello(SessId, ssl:tls_version(Version),
- ConnectionStates1, ServerHelloExt),
- State = server_hello(ServerHello,
- State1#state{handshake_env = HsEnv#handshake_env{expecting_next_protocol_negotiation =
- NextProtocols =/= undefined}}, Connection),
- case Type of
- new ->
- new_server_hello(ServerHello, State, Connection);
- resumed ->
- resumed_server_hello(State, Connection)
- end.
-
-update_server_random(#{pending_read := #{security_parameters := ReadSecParams0} =
- ReadState0,
- pending_write := #{security_parameters := WriteSecParams0} =
- WriteState0} = ConnectionStates,
- Version, HighestVersion) ->
- ReadRandom = override_server_random(
- ReadSecParams0#security_parameters.server_random,
- Version,
- HighestVersion),
- WriteRandom = override_server_random(
- WriteSecParams0#security_parameters.server_random,
- Version,
- HighestVersion),
- ReadSecParams = ReadSecParams0#security_parameters{server_random = ReadRandom},
- WriteSecParams = WriteSecParams0#security_parameters{server_random = WriteRandom},
- ReadState = ReadState0#{security_parameters => ReadSecParams},
- WriteState = WriteState0#{security_parameters => WriteSecParams},
-
- ConnectionStates#{pending_read => ReadState, pending_write => WriteState}.
-
-%% TLS 1.3 - Section 4.1.3
-%%
-%% If negotiating TLS 1.2, TLS 1.3 servers MUST set the last eight bytes
-%% of their Random value to the bytes:
-%%
-%% 44 4F 57 4E 47 52 44 01
-%%
-%% If negotiating TLS 1.1 or below, TLS 1.3 servers MUST and TLS 1.2
-%% servers SHOULD set the last eight bytes of their Random value to the
-%% bytes:
-%%
-%% 44 4F 57 4E 47 52 44 00
-override_server_random(<<Random0:24/binary,_:8/binary>> = Random, {M,N}, {Major,Minor})
- when Major > 3 orelse Major =:= 3 andalso Minor >= 4 -> %% TLS 1.3 or above
- if M =:= 3 andalso N =:= 3 -> %% Negotating TLS 1.2
- Down = ?RANDOM_OVERRIDE_TLS12,
- <<Random0/binary,Down/binary>>;
- M =:= 3 andalso N < 3 -> %% Negotating TLS 1.1 or prior
- Down = ?RANDOM_OVERRIDE_TLS11,
- <<Random0/binary,Down/binary>>;
- true ->
- Random
- end;
-override_server_random(<<Random0:24/binary,_:8/binary>> = Random, {M,N}, {Major,Minor})
- when Major =:= 3 andalso Minor =:= 3 -> %% TLS 1.2
- if M =:= 3 andalso N < 3 -> %% Negotating TLS 1.1 or prior
- Down = ?RANDOM_OVERRIDE_TLS11,
- <<Random0/binary,Down/binary>>;
- true ->
- Random
- end;
-override_server_random(Random, _, _) ->
- Random.
-
-new_server_hello(#server_hello{cipher_suite = CipherSuite,
- compression_method = Compression,
- session_id = SessionId},
- #state{session = Session0,
- connection_env = #connection_env{negotiated_version = Version}} = State0, Connection) ->
- try server_certify_and_key_exchange(State0, Connection) of
- #state{} = State1 ->
- {State, Actions} = server_hello_done(State1, Connection),
- Session =
- Session0#session{session_id = SessionId,
- cipher_suite = CipherSuite,
- compression_method = Compression},
- Connection:next_event(certify, no_record, State#state{session = Session}, Actions)
- catch
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, hello, State0)
- end.
-
-resumed_server_hello(#state{session = Session,
- connection_states = ConnectionStates0,
- connection_env = #connection_env{negotiated_version = Version}} = State0, Connection) ->
-
- case ssl_handshake:master_secret(ssl:tls_version(Version), Session,
- ConnectionStates0, server) of
- {_, ConnectionStates1} ->
- State1 = State0#state{connection_states = ConnectionStates1,
- session = Session},
- {State, Actions} =
- finalize_handshake(State1, abbreviated, Connection),
- Connection:next_event(abbreviated, no_record, State, Actions);
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, hello, State0)
- end.
-
-server_hello(ServerHello, State0, Connection) ->
- CipherSuite = ServerHello#server_hello.cipher_suite,
- #{key_exchange := KeyAlgorithm} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
- #state{handshake_env = HsEnv} = State = Connection:queue_handshake(ServerHello, State0),
- State#state{handshake_env = HsEnv#handshake_env{kex_algorithm = KeyAlgorithm}}.
-
-server_hello_done(State, Connection) ->
- HelloDone = ssl_handshake:server_hello_done(),
- Connection:send_handshake(HelloDone, State).
-
-handle_peer_cert(Role, PeerCert, PublicKeyInfo,
- #state{handshake_env = HsEnv,
- session = #session{cipher_suite = CipherSuite} = Session} = State0,
- Connection, Actions) ->
- State1 = State0#state{handshake_env = HsEnv#handshake_env{public_key_info = PublicKeyInfo},
- session =
- Session#session{peer_certificate = PeerCert}},
- #{key_exchange := KeyAlgorithm} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
- State = handle_peer_cert_key(Role, PeerCert, PublicKeyInfo, KeyAlgorithm, State1),
- Connection:next_event(certify, no_record, State, Actions).
-
-handle_peer_cert_key(client, _,
- {?'id-ecPublicKey', #'ECPoint'{point = _ECPoint} = PublicKey,
- PublicKeyParams},
- KeyAlg, #state{handshake_env = HsEnv,
- session = Session} = State) when KeyAlg == ecdh_rsa;
- KeyAlg == ecdh_ecdsa ->
- ECDHKey = public_key:generate_key(PublicKeyParams),
- PremasterSecret = ssl_handshake:premaster_secret(PublicKey, ECDHKey),
- master_secret(PremasterSecret, State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKey},
- session = Session#session{ecc = PublicKeyParams}});
-handle_peer_cert_key(_, _, _, _, State) ->
- State.
-
-certify_client(#state{static_env = #static_env{role = client,
- cert_db = CertDbHandle,
- cert_db_ref = CertDbRef},
- client_certificate_requested = true,
- session = #session{own_certificate = OwnCert}}
- = State, Connection) ->
- Certificate = ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, client),
- Connection:queue_handshake(Certificate, State);
-certify_client(#state{client_certificate_requested = false} = State, _) ->
- State.
-
-verify_client_cert(#state{static_env = #static_env{role = client},
- handshake_env = #handshake_env{tls_handshake_history = Hist,
- cert_hashsign_algorithm = HashSign},
- connection_env = #connection_env{negotiated_version = Version,
- private_key = PrivateKey},
- client_certificate_requested = true,
- session = #session{master_secret = MasterSecret,
- own_certificate = OwnCert}} = State, Connection) ->
-
- case ssl_handshake:client_certificate_verify(OwnCert, MasterSecret,
- ssl:tls_version(Version), HashSign, PrivateKey, Hist) of
- #certificate_verify{} = Verified ->
- Connection:queue_handshake(Verified, State);
- ignore ->
- State;
- #alert{} = Alert ->
- throw(Alert)
- end;
-verify_client_cert(#state{client_certificate_requested = false} = State, _) ->
- State.
-
-client_certify_and_key_exchange(#state{connection_env = #connection_env{negotiated_version = Version}} =
- State0, Connection) ->
- try do_client_certify_and_key_exchange(State0, Connection) of
- State1 = #state{} ->
- {State2, Actions} = finalize_handshake(State1, certify, Connection),
- State = State2#state{
- %% Reinitialize
- client_certificate_requested = false},
- Connection:next_event(cipher, no_record, State, Actions)
- catch
- throw:#alert{} = Alert ->
- handle_own_alert(Alert, Version, certify, State0)
- end.
-
-do_client_certify_and_key_exchange(State0, Connection) ->
- State1 = certify_client(State0, Connection),
- State2 = key_exchange(State1, Connection),
- verify_client_cert(State2, Connection).
-
-server_certify_and_key_exchange(State0, Connection) ->
- State1 = certify_server(State0, Connection),
- State2 = key_exchange(State1, Connection),
- request_client_cert(State2, Connection).
-
-certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS},
- #state{connection_env = #connection_env{private_key = Key},
- handshake_env = #handshake_env{client_hello_version = {Major, Minor} = Version}}
- = State, Connection) ->
- FakeSecret = make_premaster_secret(Version, rsa),
- %% Countermeasure for Bleichenbacher attack always provide some kind of premaster secret
- %% and fail handshake later.RFC 5246 section 7.4.7.1.
- PremasterSecret =
- try ssl_handshake:premaster_secret(EncPMS, Key) of
- Secret when erlang:byte_size(Secret) == ?NUM_OF_PREMASTERSECRET_BYTES ->
- case Secret of
- <<?BYTE(Major), ?BYTE(Minor), Rest/binary>> -> %% Correct
- <<?BYTE(Major), ?BYTE(Minor), Rest/binary>>;
- <<?BYTE(_), ?BYTE(_), Rest/binary>> -> %% Version mismatch
- <<?BYTE(Major), ?BYTE(Minor), Rest/binary>>
- end;
- _ -> %% erlang:byte_size(Secret) =/= ?NUM_OF_PREMASTERSECRET_BYTES
- FakeSecret
- catch
- #alert{description = ?DECRYPT_ERROR} ->
- FakeSecret
- end,
- calculate_master_secret(PremasterSecret, State, Connection, certify, cipher);
-certify_client_key_exchange(#client_diffie_hellman_public{dh_public = ClientPublicDhKey},
- #state{handshake_env = #handshake_env{diffie_hellman_params = #'DHParameter'{} = Params,
- kex_keys = {_, ServerDhPrivateKey}}
- } = State,
- Connection) ->
- PremasterSecret = ssl_handshake:premaster_secret(ClientPublicDhKey, ServerDhPrivateKey, Params),
- calculate_master_secret(PremasterSecret, State, Connection, certify, cipher);
-
-certify_client_key_exchange(#client_ec_diffie_hellman_public{dh_public = ClientPublicEcDhPoint},
- #state{handshake_env = #handshake_env{kex_keys = ECDHKey}} = State, Connection) ->
- PremasterSecret = ssl_handshake:premaster_secret(#'ECPoint'{point = ClientPublicEcDhPoint}, ECDHKey),
- calculate_master_secret(PremasterSecret, State, Connection, certify, cipher);
-certify_client_key_exchange(#client_psk_identity{} = ClientKey,
- #state{ssl_options =
- #{user_lookup_fun := PSKLookup}} = State0,
- Connection) ->
- PremasterSecret = ssl_handshake:premaster_secret(ClientKey, PSKLookup),
- calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher);
-certify_client_key_exchange(#client_dhe_psk_identity{} = ClientKey,
- #state{handshake_env = #handshake_env{diffie_hellman_params = #'DHParameter'{} = Params,
- kex_keys = {_, ServerDhPrivateKey}},
- ssl_options =
- #{user_lookup_fun := PSKLookup}} = State0,
- Connection) ->
- PremasterSecret =
- ssl_handshake:premaster_secret(ClientKey, ServerDhPrivateKey, Params, PSKLookup),
- calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher);
-certify_client_key_exchange(#client_ecdhe_psk_identity{} = ClientKey,
- #state{handshake_env = #handshake_env{kex_keys = ServerEcDhPrivateKey},
- ssl_options =
- #{user_lookup_fun := PSKLookup}} = State,
- Connection) ->
- PremasterSecret =
- ssl_handshake:premaster_secret(ClientKey, ServerEcDhPrivateKey, PSKLookup),
- calculate_master_secret(PremasterSecret, State, Connection, certify, cipher);
-certify_client_key_exchange(#client_rsa_psk_identity{} = ClientKey,
- #state{connection_env = #connection_env{private_key = Key},
- ssl_options =
- #{user_lookup_fun := PSKLookup}} = State0,
- Connection) ->
- PremasterSecret = ssl_handshake:premaster_secret(ClientKey, Key, PSKLookup),
- calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher);
-certify_client_key_exchange(#client_srp_public{} = ClientKey,
- #state{handshake_env = #handshake_env{srp_params = Params,
- kex_keys = Key}
- } = State0, Connection) ->
- PremasterSecret = ssl_handshake:premaster_secret(ClientKey, Key, Params),
- calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher).
-
-certify_server(#state{handshake_env = #handshake_env{kex_algorithm = KexAlg}} =
- State, _) when KexAlg == dh_anon;
- KexAlg == ecdh_anon;
- KexAlg == psk;
- KexAlg == dhe_psk;
- KexAlg == ecdhe_psk;
- KexAlg == srp_anon ->
- State;
-certify_server(#state{static_env = #static_env{cert_db = CertDbHandle,
- cert_db_ref = CertDbRef},
- session = #session{own_certificate = OwnCert}} = State, Connection) ->
- case ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, server) of
- Cert = #certificate{} ->
- Connection:queue_handshake(Cert, State);
- Alert = #alert{} ->
- throw(Alert)
- end.
-
-key_exchange(#state{static_env = #static_env{role = server},
- handshake_env = #handshake_env{kex_algorithm = rsa}} = State,_) ->
- State;
-key_exchange(#state{static_env = #static_env{role = server},
- handshake_env = #handshake_env{kex_algorithm = KexAlg,
- diffie_hellman_params = #'DHParameter'{} = Params,
- hashsign_algorithm = HashSignAlgo},
- connection_env = #connection_env{negotiated_version = Version,
- private_key = PrivateKey},
- connection_states = ConnectionStates0} = State0, Connection)
- when KexAlg == dhe_dss;
- KexAlg == dhe_rsa;
- KexAlg == dh_anon ->
- DHKeys = public_key:generate_key(Params),
- #{security_parameters := SecParams} =
- ssl_record:pending_connection_state(ConnectionStates0, read),
- #security_parameters{client_random = ClientRandom,
- server_random = ServerRandom} = SecParams,
- Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), {dh, DHKeys, Params,
- HashSignAlgo, ClientRandom,
- ServerRandom,
- PrivateKey}),
- #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0),
- State#state{handshake_env = HsEnv#handshake_env{kex_keys = DHKeys}};
-key_exchange(#state{static_env = #static_env{role = server},
- handshake_env = #handshake_env{kex_algorithm = KexAlg} = HsEnv,
- connection_env = #connection_env{private_key = #'ECPrivateKey'{parameters = ECCurve} = Key},
- session = Session} = State, _)
- when KexAlg == ecdh_ecdsa;
- KexAlg == ecdh_rsa ->
- State#state{handshake_env = HsEnv#handshake_env{kex_keys = Key},
- session = Session#session{ecc = ECCurve}};
-key_exchange(#state{static_env = #static_env{role = server},
- handshake_env = #handshake_env{kex_algorithm = KexAlg,
- hashsign_algorithm = HashSignAlgo},
- connection_env = #connection_env{negotiated_version = Version,
- private_key = PrivateKey},
- session = #session{ecc = ECCCurve},
- connection_states = ConnectionStates0} = State0, Connection)
- when KexAlg == ecdhe_ecdsa;
- KexAlg == ecdhe_rsa;
- KexAlg == ecdh_anon ->
-
- ECDHKeys = public_key:generate_key(ECCCurve),
- #{security_parameters := SecParams} =
- ssl_record:pending_connection_state(ConnectionStates0, read),
- #security_parameters{client_random = ClientRandom,
- server_random = ServerRandom} = SecParams,
- Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version),
- {ecdh, ECDHKeys,
- HashSignAlgo, ClientRandom,
- ServerRandom,
- PrivateKey}),
- #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0),
- State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys}};
-key_exchange(#state{static_env = #static_env{role = server},
- handshake_env = #handshake_env{kex_algorithm = psk},
- ssl_options = #{psk_identity := undefined}} = State, _) ->
- State;
-key_exchange(#state{static_env = #static_env{role = server},
- ssl_options = #{psk_identity := PskIdentityHint},
- handshake_env = #handshake_env{kex_algorithm = psk,
- hashsign_algorithm = HashSignAlgo},
- connection_env = #connection_env{negotiated_version = Version,
- private_key = PrivateKey},
- connection_states = ConnectionStates0} = State0, Connection) ->
- #{security_parameters := SecParams} =
- ssl_record:pending_connection_state(ConnectionStates0, read),
- #security_parameters{client_random = ClientRandom,
- server_random = ServerRandom} = SecParams,
- Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version),
- {psk, PskIdentityHint,
- HashSignAlgo, ClientRandom,
- ServerRandom,
- PrivateKey}),
- Connection:queue_handshake(Msg, State0);
-key_exchange(#state{static_env = #static_env{role = server},
- ssl_options = #{psk_identity := PskIdentityHint},
- handshake_env = #handshake_env{kex_algorithm = dhe_psk,
- diffie_hellman_params = #'DHParameter'{} = Params,
- hashsign_algorithm = HashSignAlgo},
- connection_env = #connection_env{negotiated_version = Version,
- private_key = PrivateKey},
- connection_states = ConnectionStates0
- } = State0, Connection) ->
- DHKeys = public_key:generate_key(Params),
- #{security_parameters := SecParams} =
- ssl_record:pending_connection_state(ConnectionStates0, read),
- #security_parameters{client_random = ClientRandom,
- server_random = ServerRandom} = SecParams,
- Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version),
- {dhe_psk,
- PskIdentityHint, DHKeys, Params,
- HashSignAlgo, ClientRandom,
- ServerRandom,
- PrivateKey}),
- #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0),
- State#state{handshake_env = HsEnv#handshake_env{kex_keys = DHKeys}};
-key_exchange(#state{static_env = #static_env{role = server},
- ssl_options = #{psk_identity := PskIdentityHint},
- handshake_env = #handshake_env{kex_algorithm = ecdhe_psk,
- hashsign_algorithm = HashSignAlgo},
- connection_env = #connection_env{negotiated_version = Version,
- private_key = PrivateKey},
- session = #session{ecc = ECCCurve},
- connection_states = ConnectionStates0
- } = State0, Connection) ->
- ECDHKeys = public_key:generate_key(ECCCurve),
- #{security_parameters := SecParams} =
- ssl_record:pending_connection_state(ConnectionStates0, read),
- #security_parameters{client_random = ClientRandom,
- server_random = ServerRandom} = SecParams,
- Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version),
- {ecdhe_psk,
- PskIdentityHint, ECDHKeys,
- HashSignAlgo, ClientRandom,
- ServerRandom,
- PrivateKey}),
- #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0),
- State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys}};
-key_exchange(#state{static_env = #static_env{role = server},
- handshake_env = #handshake_env{kex_algorithm = rsa_psk},
- ssl_options = #{psk_identity := undefined}} = State, _) ->
- State;
-key_exchange(#state{static_env = #static_env{role = server},
- ssl_options = #{psk_identity := PskIdentityHint},
- handshake_env = #handshake_env{kex_algorithm = rsa_psk,
- hashsign_algorithm = HashSignAlgo},
- connection_env = #connection_env{negotiated_version = Version,
- private_key = PrivateKey},
- connection_states = ConnectionStates0
- } = State0, Connection) ->
- #{security_parameters := SecParams} =
- ssl_record:pending_connection_state(ConnectionStates0, read),
- #security_parameters{client_random = ClientRandom,
- server_random = ServerRandom} = SecParams,
- Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version),
- {psk, PskIdentityHint,
- HashSignAlgo, ClientRandom,
- ServerRandom,
- PrivateKey}),
- Connection:queue_handshake(Msg, State0);
-key_exchange(#state{static_env = #static_env{role = server},
- ssl_options = #{user_lookup_fun := LookupFun},
- handshake_env = #handshake_env{kex_algorithm = KexAlg,
- hashsign_algorithm = HashSignAlgo},
- connection_env = #connection_env{negotiated_version = Version,
- private_key = PrivateKey},
- session = #session{srp_username = Username},
- connection_states = ConnectionStates0
- } = State0, Connection)
- when KexAlg == srp_dss;
- KexAlg == srp_rsa;
- KexAlg == srp_anon ->
- SrpParams = handle_srp_identity(Username, LookupFun),
- Keys = case generate_srp_server_keys(SrpParams, 0) of
- Alert = #alert{} ->
- throw(Alert);
- Keys0 = {_,_} ->
- Keys0
- end,
- #{security_parameters := SecParams} =
- ssl_record:pending_connection_state(ConnectionStates0, read),
- #security_parameters{client_random = ClientRandom,
- server_random = ServerRandom} = SecParams,
- Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version),
- {srp, Keys, SrpParams,
- HashSignAlgo, ClientRandom,
- ServerRandom,
- PrivateKey}),
- #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0),
- State#state{handshake_env = HsEnv#handshake_env{srp_params = SrpParams,
- kex_keys = Keys}};
-key_exchange(#state{static_env = #static_env{role = client},
- handshake_env = #handshake_env{kex_algorithm = rsa,
- public_key_info = PublicKeyInfo,
- premaster_secret = PremasterSecret},
- connection_env = #connection_env{negotiated_version = Version}
- } = State0, Connection) ->
- Msg = rsa_key_exchange(ssl:tls_version(Version), PremasterSecret, PublicKeyInfo),
- Connection:queue_handshake(Msg, State0);
-key_exchange(#state{static_env = #static_env{role = client},
- handshake_env = #handshake_env{kex_algorithm = KexAlg,
- kex_keys = {DhPubKey, _}},
- connection_env = #connection_env{negotiated_version = Version}
- } = State0, Connection)
- when KexAlg == dhe_dss;
- KexAlg == dhe_rsa;
- KexAlg == dh_anon ->
- Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {dh, DhPubKey}),
- Connection:queue_handshake(Msg, State0);
-
-key_exchange(#state{static_env = #static_env{role = client},
- handshake_env = #handshake_env{kex_algorithm = KexAlg,
- kex_keys = #'ECPrivateKey'{parameters = ECCurve} = Key},
- connection_env = #connection_env{negotiated_version = Version},
- session = Session
- } = State0, Connection)
- when KexAlg == ecdhe_ecdsa;
- KexAlg == ecdhe_rsa;
- KexAlg == ecdh_ecdsa;
- KexAlg == ecdh_rsa;
- KexAlg == ecdh_anon ->
- Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {ecdh, Key}),
- Connection:queue_handshake(Msg, State0#state{session = Session#session{ecc = ECCurve}});
-key_exchange(#state{static_env = #static_env{role = client},
- handshake_env = #handshake_env{kex_algorithm = psk},
- connection_env = #connection_env{negotiated_version = Version},
- ssl_options = #{psk_identity := PSKIdentity}} = State0, Connection) ->
- Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version),
- {psk, PSKIdentity}),
- Connection:queue_handshake(Msg, State0);
-key_exchange(#state{static_env = #static_env{role = client},
- handshake_env = #handshake_env{kex_algorithm = dhe_psk,
- kex_keys = {DhPubKey, _}},
- connection_env = #connection_env{negotiated_version = Version},
- ssl_options = #{psk_identity := PSKIdentity}} = State0, Connection) ->
- Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version),
- {dhe_psk,
- PSKIdentity, DhPubKey}),
- Connection:queue_handshake(Msg, State0);
-
-key_exchange(#state{static_env = #static_env{role = client},
- handshake_env = #handshake_env{kex_algorithm = ecdhe_psk,
- kex_keys = ECDHKeys},
- connection_env = #connection_env{negotiated_version = Version},
- ssl_options = #{psk_identity := PSKIdentity}} = State0, Connection) ->
- Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version),
- {ecdhe_psk,
- PSKIdentity, ECDHKeys}),
- Connection:queue_handshake(Msg, State0);
-
-key_exchange(#state{static_env = #static_env{role = client},
- handshake_env = #handshake_env{kex_algorithm = rsa_psk,
- public_key_info = PublicKeyInfo,
- premaster_secret = PremasterSecret},
- connection_env = #connection_env{negotiated_version = Version},
- ssl_options = #{psk_identity := PSKIdentity}}
- = State0, Connection) ->
- Msg = rsa_psk_key_exchange(ssl:tls_version(Version), PSKIdentity,
- PremasterSecret, PublicKeyInfo),
- Connection:queue_handshake(Msg, State0);
-key_exchange(#state{static_env = #static_env{role = client},
- handshake_env = #handshake_env{kex_algorithm = KexAlg,
- kex_keys = {ClientPubKey, _}},
- connection_env = #connection_env{negotiated_version = Version}}
- = State0, Connection)
- when KexAlg == srp_dss;
- KexAlg == srp_rsa;
- KexAlg == srp_anon ->
- Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {srp, ClientPubKey}),
- Connection:queue_handshake(Msg, State0).
-
-rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo = {Algorithm, _, _})
- when Algorithm == ?rsaEncryption;
- Algorithm == ?md2WithRSAEncryption;
- Algorithm == ?md5WithRSAEncryption;
- Algorithm == ?sha1WithRSAEncryption;
- Algorithm == ?sha224WithRSAEncryption;
- Algorithm == ?sha256WithRSAEncryption;
- Algorithm == ?sha384WithRSAEncryption;
- Algorithm == ?sha512WithRSAEncryption
- ->
- ssl_handshake:key_exchange(client, ssl:tls_version(Version),
- {premaster_secret, PremasterSecret,
- PublicKeyInfo});
-rsa_key_exchange(_, _, _) ->
- throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, pub_key_is_not_rsa)).
-
-rsa_psk_key_exchange(Version, PskIdentity, PremasterSecret,
- PublicKeyInfo = {Algorithm, _, _})
- when Algorithm == ?rsaEncryption;
- Algorithm == ?md2WithRSAEncryption;
- Algorithm == ?md5WithRSAEncryption;
- Algorithm == ?sha1WithRSAEncryption;
- Algorithm == ?sha224WithRSAEncryption;
- Algorithm == ?sha256WithRSAEncryption;
- Algorithm == ?sha384WithRSAEncryption;
- Algorithm == ?sha512WithRSAEncryption
- ->
- ssl_handshake:key_exchange(client, ssl:tls_version(Version),
- {psk_premaster_secret, PskIdentity, PremasterSecret,
- PublicKeyInfo});
-rsa_psk_key_exchange(_, _, _, _) ->
- throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, pub_key_is_not_rsa)).
-
-request_client_cert(#state{handshake_env = #handshake_env{kex_algorithm = Alg}} = State, _)
- when Alg == dh_anon;
- Alg == ecdh_anon;
- Alg == psk;
- Alg == dhe_psk;
- Alg == ecdhe_psk;
- Alg == rsa_psk;
- Alg == srp_dss;
- Alg == srp_rsa;
- Alg == srp_anon ->
- State;
-
-request_client_cert(#state{static_env = #static_env{cert_db = CertDbHandle,
- cert_db_ref = CertDbRef},
- connection_env = #connection_env{negotiated_version = Version},
- ssl_options = #{verify := verify_peer,
- signature_algs := SupportedHashSigns},
- connection_states = ConnectionStates0} = State0, Connection) ->
- #{security_parameters :=
- #security_parameters{cipher_suite = CipherSuite}} =
- ssl_record:pending_connection_state(ConnectionStates0, read),
- TLSVersion = ssl:tls_version(Version),
- HashSigns = ssl_handshake:available_signature_algs(SupportedHashSigns,
- TLSVersion),
- Msg = ssl_handshake:certificate_request(CipherSuite, CertDbHandle, CertDbRef,
- HashSigns, TLSVersion),
- State = Connection:queue_handshake(Msg, State0),
- State#state{client_certificate_requested = true};
-
-request_client_cert(#state{ssl_options = #{verify := verify_none}} =
- State, _) ->
- State.
-
-calculate_master_secret(PremasterSecret,
- #state{connection_env = #connection_env{negotiated_version = Version},
- connection_states = ConnectionStates0,
- session = Session0} = State0, Connection,
- _Current, Next) ->
- case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret,
- ConnectionStates0, server) of
- {MasterSecret, ConnectionStates} ->
- Session = Session0#session{master_secret = MasterSecret},
- State = State0#state{connection_states = ConnectionStates,
- session = Session},
- Connection:next_event(Next, no_record, State);
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, certify, State0)
- end.
-
-finalize_handshake(State0, StateName, Connection) ->
- #state{connection_states = ConnectionStates0} =
- State1 = cipher_protocol(State0, Connection),
-
- ConnectionStates =
- ssl_record:activate_pending_connection_state(ConnectionStates0,
- write, Connection),
-
- State2 = State1#state{connection_states = ConnectionStates},
- State = next_protocol(State2, Connection),
- finished(State, StateName, Connection).
-
-next_protocol(#state{static_env = #static_env{role = server}} = State, _) ->
- State;
-next_protocol(#state{handshake_env = #handshake_env{negotiated_protocol = undefined}} = State, _) ->
- State;
-next_protocol(#state{handshake_env = #handshake_env{expecting_next_protocol_negotiation = false}} = State, _) ->
- State;
-next_protocol(#state{handshake_env = #handshake_env{negotiated_protocol = NextProtocol}} = State0, Connection) ->
- NextProtocolMessage = ssl_handshake:next_protocol(NextProtocol),
- Connection:queue_handshake(NextProtocolMessage, State0).
-
-cipher_protocol(State, Connection) ->
- Connection:queue_change_cipher(#change_cipher_spec{}, State).
-
-finished(#state{static_env = #static_env{role = Role},
- handshake_env = #handshake_env{tls_handshake_history = Hist},
- connection_env = #connection_env{negotiated_version = Version},
- session = Session,
- connection_states = ConnectionStates0} = State0,
- StateName, Connection) ->
- MasterSecret = Session#session.master_secret,
- Finished = ssl_handshake:finished(ssl:tls_version(Version), Role,
- get_current_prf(ConnectionStates0, write),
- MasterSecret, Hist),
- ConnectionStates = save_verify_data(Role, Finished, ConnectionStates0, StateName),
- Connection:send_handshake(Finished, State0#state{connection_states =
- ConnectionStates}).
-
-save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, certify) ->
- ssl_record:set_client_verify_data(current_write, Data, ConnectionStates);
-save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, cipher) ->
- ssl_record:set_server_verify_data(current_both, Data, ConnectionStates);
-save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, abbreviated) ->
- ssl_record:set_client_verify_data(current_both, Data, ConnectionStates);
-save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, abbreviated) ->
- ssl_record:set_server_verify_data(current_write, Data, ConnectionStates).
-
-calculate_secret(#server_dh_params{dh_p = Prime, dh_g = Base,
- dh_y = ServerPublicDhKey} = Params,
- #state{handshake_env = HsEnv} = State, Connection) ->
- Keys = {_, PrivateDhKey} = crypto:generate_key(dh, [Prime, Base]),
- PremasterSecret =
- ssl_handshake:premaster_secret(ServerPublicDhKey, PrivateDhKey, Params),
- calculate_master_secret(PremasterSecret,
- State#state{handshake_env = HsEnv#handshake_env{kex_keys = Keys}},
- Connection, certify, certify);
-
-calculate_secret(#server_ecdh_params{curve = ECCurve, public = ECServerPubKey},
- #state{handshake_env = HsEnv,
- session = Session} = State, Connection) ->
- ECDHKeys = public_key:generate_key(ECCurve),
- PremasterSecret =
- ssl_handshake:premaster_secret(#'ECPoint'{point = ECServerPubKey}, ECDHKeys),
- calculate_master_secret(PremasterSecret,
- State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys},
- session = Session#session{ecc = ECCurve}},
- Connection, certify, certify);
-
-calculate_secret(#server_psk_params{
- hint = IdentityHint},
- #state{handshake_env = HsEnv} = State, Connection) ->
- %% store for later use
- Connection:next_event(certify, no_record,
- State#state{handshake_env =
- HsEnv#handshake_env{server_psk_identity = IdentityHint}});
-
-calculate_secret(#server_dhe_psk_params{
- dh_params = #server_dh_params{dh_p = Prime, dh_g = Base}} = ServerKey,
- #state{handshake_env = HsEnv,
- ssl_options = #{user_lookup_fun := PSKLookup}} =
- State, Connection) ->
- Keys = {_, PrivateDhKey} =
- crypto:generate_key(dh, [Prime, Base]),
- PremasterSecret = ssl_handshake:premaster_secret(ServerKey, PrivateDhKey, PSKLookup),
- calculate_master_secret(PremasterSecret, State#state{handshake_env = HsEnv#handshake_env{kex_keys = Keys}},
- Connection, certify, certify);
-
-calculate_secret(#server_ecdhe_psk_params{
- dh_params = #server_ecdh_params{curve = ECCurve}} = ServerKey,
- #state{ssl_options = #{user_lookup_fun := PSKLookup}} =
- #state{handshake_env = HsEnv,
- session = Session} = State, Connection) ->
- ECDHKeys = public_key:generate_key(ECCurve),
-
- PremasterSecret = ssl_handshake:premaster_secret(ServerKey, ECDHKeys, PSKLookup),
- calculate_master_secret(PremasterSecret,
- State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys},
- session = Session#session{ecc = ECCurve}},
- Connection, certify, certify);
-
-calculate_secret(#server_srp_params{srp_n = Prime, srp_g = Generator} = ServerKey,
- #state{handshake_env = HsEnv,
- ssl_options = #{srp_identity := SRPId}} = State,
- Connection) ->
- Keys = generate_srp_client_keys(Generator, Prime, 0),
- PremasterSecret = ssl_handshake:premaster_secret(ServerKey, Keys, SRPId),
- calculate_master_secret(PremasterSecret, State#state{handshake_env = HsEnv#handshake_env{kex_keys = Keys}}, Connection,
- certify, certify).
-
-master_secret(#alert{} = Alert, _) ->
- Alert;
-master_secret(PremasterSecret, #state{static_env = #static_env{role = Role},
- connection_env = #connection_env{negotiated_version = Version},
- session = Session,
- connection_states = ConnectionStates0} = State) ->
- case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret,
- ConnectionStates0, Role) of
- {MasterSecret, ConnectionStates} ->
- State#state{
- session =
- Session#session{master_secret = MasterSecret},
- connection_states = ConnectionStates};
- #alert{} = Alert ->
- Alert
- end.
-
-generate_srp_server_keys(_SrpParams, 10) ->
- ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
-generate_srp_server_keys(SrpParams =
- #srp_user{generator = Generator, prime = Prime,
- verifier = Verifier}, N) ->
- try crypto:generate_key(srp, {host, [Verifier, Generator, Prime, '6a']}) of
- Keys ->
- Keys
- catch
- error:_ ->
- generate_srp_server_keys(SrpParams, N+1)
- end.
-
-generate_srp_client_keys(_Generator, _Prime, 10) ->
- ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
-generate_srp_client_keys(Generator, Prime, N) ->
-
- try crypto:generate_key(srp, {user, [Generator, Prime, '6a']}) of
- Keys ->
- Keys
- catch
- error:_ ->
- generate_srp_client_keys(Generator, Prime, N+1)
- end.
-
-handle_srp_identity(Username, {Fun, UserState}) ->
- case Fun(srp, Username, UserState) of
- {ok, {SRPParams, Salt, DerivedKey}}
- when is_atom(SRPParams), is_binary(Salt), is_binary(DerivedKey) ->
- {Generator, Prime} = ssl_srp_primes:get_srp_params(SRPParams),
- Verifier = crypto:mod_pow(Generator, DerivedKey, Prime),
- #srp_user{generator = Generator, prime = Prime,
- salt = Salt, verifier = Verifier};
- #alert{} = Alert ->
- throw(Alert);
- _ ->
- throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER))
- end.
-
-
-cipher_role(client, Data, Session, #state{connection_states = ConnectionStates0} = State0,
- Connection) ->
- ConnectionStates = ssl_record:set_server_verify_data(current_both, Data,
- ConnectionStates0),
- {Record, State} = prepare_connection(State0#state{session = Session,
- connection_states = ConnectionStates},
- Connection),
- Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close}]);
-cipher_role(server, Data, Session, #state{connection_states = ConnectionStates0} = State0,
- Connection) ->
- ConnectionStates1 = ssl_record:set_client_verify_data(current_read, Data,
- ConnectionStates0),
- {State1, Actions} =
- finalize_handshake(State0#state{connection_states = ConnectionStates1,
- session = Session}, cipher, Connection),
- {Record, State} = prepare_connection(State1, Connection),
- Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close} | Actions]).
-
-is_anonymous(KexAlg) when KexAlg == dh_anon;
- KexAlg == ecdh_anon;
- KexAlg == psk;
- KexAlg == dhe_psk;
- KexAlg == ecdhe_psk;
- KexAlg == rsa_psk;
- KexAlg == srp_anon ->
- true;
-is_anonymous(_) ->
- false.
-
-get_current_prf(CStates, Direction) ->
- #{security_parameters := SecParams} = ssl_record:current_connection_state(CStates, Direction),
- SecParams#security_parameters.prf_algorithm.
-get_pending_prf(CStates, Direction) ->
- #{security_parameters := SecParams} = ssl_record:pending_connection_state(CStates, Direction),
- SecParams#security_parameters.prf_algorithm.
-
-opposite_role(client) ->
- server;
-opposite_role(server) ->
- client.
-
-record_cb(tls_connection) ->
- tls_record;
-record_cb(dtls_connection) ->
- dtls_record.
-
-call(FsmPid, Event) ->
- try gen_statem:call(FsmPid, Event)
- catch
- exit:{noproc, _} ->
- {error, closed};
- exit:{normal, _} ->
- {error, closed};
- exit:{{shutdown, _},_} ->
- {error, closed}
- end.
-
-get_socket_opts(_, _,_,[], _, Acc) ->
- {ok, Acc};
-get_socket_opts(Connection, Transport, Socket, [mode | Tags], SockOpts, Acc) ->
- get_socket_opts(Connection, Transport, Socket, Tags, SockOpts,
- [{mode, SockOpts#socket_options.mode} | Acc]);
-get_socket_opts(Connection, Transport, Socket, [packet | Tags], SockOpts, Acc) ->
- case SockOpts#socket_options.packet of
- {Type, headers} ->
- get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]);
- Type ->
- get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc])
- end;
-get_socket_opts(Connection, Transport, Socket, [header | Tags], SockOpts, Acc) ->
- get_socket_opts(Connection, Transport, Socket, Tags, SockOpts,
- [{header, SockOpts#socket_options.header} | Acc]);
-get_socket_opts(Connection, Transport, Socket, [active | Tags], SockOpts, Acc) ->
- get_socket_opts(Connection, Transport, Socket, Tags, SockOpts,
- [{active, SockOpts#socket_options.active} | Acc]);
-get_socket_opts(Connection, Transport, Socket, [Tag | Tags], SockOpts, Acc) ->
- case Connection:getopts(Transport, Socket, [Tag]) of
- {ok, [Opt]} ->
- get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [Opt | Acc]);
- {error, Reason} ->
- {error, {options, {socket_options, Tag, Reason}}}
- end;
-get_socket_opts(_,_, _,Opts, _,_) ->
- {error, {options, {socket_options, Opts, function_clause}}}.
-
-set_socket_opts(_,_,_, [], SockOpts, []) ->
- {ok, SockOpts};
-set_socket_opts(ConnectionCb, Transport, Socket, [], SockOpts, Other) ->
- %% Set non emulated options
- try ConnectionCb:setopts(Transport, Socket, Other) of
- ok ->
- {ok, SockOpts};
- {error, InetError} ->
- {{error, {options, {socket_options, Other, InetError}}}, SockOpts}
- catch
- _:Error ->
- %% So that inet behavior does not crash our process
- {{error, {options, {socket_options, Other, Error}}}, SockOpts}
- end;
-
-set_socket_opts(ConnectionCb, Transport,Socket, [{mode, Mode}| Opts], SockOpts, Other)
- when Mode == list; Mode == binary ->
- set_socket_opts(ConnectionCb, Transport, Socket, Opts,
- SockOpts#socket_options{mode = Mode}, Other);
-set_socket_opts(_, _, _, [{mode, _} = Opt| _], SockOpts, _) ->
- {{error, {options, {socket_options, Opt}}}, SockOpts};
-set_socket_opts(ConnectionCb, Transport,Socket, [{packet, Packet}| Opts], SockOpts, Other)
- when Packet == raw;
- Packet == 0;
- Packet == 1;
- Packet == 2;
- Packet == 4;
- Packet == asn1;
- Packet == cdr;
- Packet == sunrm;
- Packet == fcgi;
- Packet == tpkt;
- Packet == line;
- Packet == http;
- Packet == httph;
- Packet == http_bin;
- Packet == httph_bin ->
- set_socket_opts(ConnectionCb, Transport, Socket, Opts,
- SockOpts#socket_options{packet = Packet}, Other);
-set_socket_opts(_, _, _, [{packet, _} = Opt| _], SockOpts, _) ->
- {{error, {options, {socket_options, Opt}}}, SockOpts};
-set_socket_opts(ConnectionCb, Transport, Socket, [{header, Header}| Opts], SockOpts, Other)
- when is_integer(Header) ->
- set_socket_opts(ConnectionCb, Transport, Socket, Opts,
- SockOpts#socket_options{header = Header}, Other);
-set_socket_opts(_, _, _, [{header, _} = Opt| _], SockOpts, _) ->
- {{error,{options, {socket_options, Opt}}}, SockOpts};
-set_socket_opts(ConnectionCb, Transport, Socket, [{active, Active}| Opts], SockOpts, Other)
- when Active == once;
- Active == true;
- Active == false ->
- set_socket_opts(ConnectionCb, Transport, Socket, Opts,
- SockOpts#socket_options{active = Active}, Other);
-set_socket_opts(ConnectionCb, Transport, Socket, [{active, Active1} = Opt| Opts],
- SockOpts=#socket_options{active = Active0}, Other)
- when Active1 >= -32768, Active1 =< 32767 ->
- Active = if
- is_integer(Active0), Active0 + Active1 < -32768 ->
- error;
- is_integer(Active0), Active0 + Active1 =< 0 ->
- false;
- is_integer(Active0), Active0 + Active1 > 32767 ->
- error;
- Active1 =< 0 ->
- false;
- is_integer(Active0) ->
- Active0 + Active1;
- true ->
- Active1
- end,
- case Active of
- error ->
- {{error, {options, {socket_options, Opt}} }, SockOpts};
- _ ->
- set_socket_opts(ConnectionCb, Transport, Socket, Opts,
- SockOpts#socket_options{active = Active}, Other)
- end;
-set_socket_opts(_,_, _, [{active, _} = Opt| _], SockOpts, _) ->
- {{error, {options, {socket_options, Opt}} }, SockOpts};
-set_socket_opts(ConnectionCb, Transport, Socket, [Opt | Opts], SockOpts, Other) ->
- set_socket_opts(ConnectionCb, Transport, Socket, Opts, SockOpts, [Opt | Other]).
-
-
-
-hibernate_after(connection = StateName,
- #state{ssl_options= #{hibernate_after := HibernateAfter}} = State,
- Actions) ->
- {next_state, StateName, State, [{timeout, HibernateAfter, hibernate} | Actions]};
-hibernate_after(StateName, State, Actions) ->
- {next_state, StateName, State, Actions}.
-
-
-terminate_alert(normal) ->
- ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY);
-terminate_alert({Reason, _}) when Reason == close;
- Reason == shutdown ->
- ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY);
-terminate_alert(_) ->
- ?ALERT_REC(?FATAL, ?INTERNAL_ERROR).
-
-handle_trusted_certs_db(#state{ssl_options =
- #{cacertfile := <<>>, cacerts := []}}) ->
- %% No trusted certs specified
- ok;
-handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref,
- cert_db = CertDb},
- ssl_options = #{cacertfile := <<>>}}) when CertDb =/= undefined ->
- %% Certs provided as DER directly can not be shared
- %% with other connections and it is safe to delete them when the connection ends.
- ssl_pkix_db:remove_trusted_certs(Ref, CertDb);
-handle_trusted_certs_db(#state{static_env = #static_env{file_ref_db = undefined}}) ->
- %% Something went wrong early (typically cacertfile does not
- %% exist) so there is nothing to handle
- ok;
-handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref,
- file_ref_db = RefDb},
- ssl_options = #{cacertfile := File}}) ->
- case ssl_pkix_db:ref_count(Ref, RefDb, -1) of
- 0 ->
- ssl_manager:clean_cert_db(Ref, File);
- _ ->
- ok
- end.
-
-prepare_connection(#state{handshake_env = #handshake_env{renegotiation = Renegotiate},
- start_or_recv_from = RecvFrom} = State0, Connection)
- when Renegotiate =/= {false, first},
- RecvFrom =/= undefined ->
- State = Connection:reinit(State0),
- {no_record, ack_connection(State)};
-prepare_connection(State0, Connection) ->
- State = Connection:reinit(State0),
- {no_record, ack_connection(State)}.
-
-ack_connection(#state{handshake_env = #handshake_env{renegotiation = {true, Initiater}} = HsEnv} = State) when Initiater == peer;
- Initiater == internal ->
- State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}};
-ack_connection(#state{handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv} = State) ->
- gen_statem:reply(From, ok),
- State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}};
-ack_connection(#state{handshake_env = #handshake_env{renegotiation = {false, first}} = HsEnv,
- start_or_recv_from = StartFrom} = State) when StartFrom =/= undefined ->
- gen_statem:reply(StartFrom, connected),
- State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined},
- start_or_recv_from = undefined};
-ack_connection(State) ->
- State.
-
-session_handle_params(#server_ecdh_params{curve = ECCurve}, Session) ->
- Session#session{ecc = ECCurve};
-session_handle_params(_, Session) ->
- Session.
-
-handle_session(server, #{reuse_sessions := true},
- _Host, _Port, Trackers, #session{is_resumable = false} = Session) ->
- Tracker = proplists:get_value(session_id_tracker, Trackers),
- server_register_session(Tracker, Session#session{is_resumable = true});
-handle_session(Role = client, #{verify := verify_peer,
- reuse_sessions := Reuse} = SslOpts,
- Host, Port, _, #session{is_resumable = false} = Session) when Reuse =/= false ->
- client_register_session(host_id(Role, Host, SslOpts), Port, Session#session{is_resumable = true},
- reg_type(Reuse));
-handle_session(_,_,_,_,_, Session) ->
- Session.
-
-reg_type(save) ->
- true;
-reg_type(true) ->
- unique.
-
-client_register_session(Host, Port, Session, Save) ->
- ssl_manager:register_session(Host, Port, Session, Save),
- Session.
-server_register_session(Tracker, Session) ->
- ssl_server_session_cache:register_session(Tracker, Session),
- Session.
-
-host_id(client, _Host, #{server_name_indication := Hostname}) when is_list(Hostname) ->
- Hostname;
-host_id(_, Host, _) ->
- Host.
-
-handle_new_session(NewId, CipherSuite, Compression,
- #state{static_env = #static_env{protocol_cb = Connection},
- session = Session0
- } = State0) ->
- Session = Session0#session{session_id = NewId,
- cipher_suite = CipherSuite,
- compression_method = Compression},
- Connection:next_event(certify, no_record, State0#state{session = Session}).
-
-handle_resumed_session(SessId, #state{static_env = #static_env{host = Host,
- port = Port,
- protocol_cb = Connection,
- session_cache = Cache,
- session_cache_cb = CacheCb},
- connection_env = #connection_env{negotiated_version = Version},
- connection_states = ConnectionStates0} = State) ->
- Session = CacheCb:lookup(Cache, {{Host, Port}, SessId}),
- case ssl_handshake:master_secret(ssl:tls_version(Version), Session,
- ConnectionStates0, client) of
- {_, ConnectionStates} ->
- Connection:next_event(abbreviated, no_record, State#state{
- connection_states = ConnectionStates,
- session = Session});
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, hello, State)
- end.
-
-make_premaster_secret({MajVer, MinVer}, rsa) ->
- Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2),
- <<?BYTE(MajVer), ?BYTE(MinVer), Rand/binary>>;
-make_premaster_secret(_, _) ->
- undefined.
-
-negotiated_hashsign(undefined, KexAlg, PubKeyInfo, Version) ->
- %% Not negotiated choose default
- case is_anonymous(KexAlg) of
- true ->
- {null, anon};
- false ->
- {PubAlg, _, _} = PubKeyInfo,
- ssl_handshake:select_hashsign_algs(undefined, PubAlg, Version)
- end;
-negotiated_hashsign(HashSign = {_, _}, _, _, _) ->
- HashSign.
-
-ssl_options_list(SslOptions) ->
- L = maps:to_list(SslOptions),
- ssl_options_list(L, []).
-
-ssl_options_list([], Acc) ->
- lists:reverse(Acc);
-%% Skip internal options, only return user options
-ssl_options_list([{protocol, _}| T], Acc) ->
- ssl_options_list(T, Acc);
-ssl_options_list([{erl_dist, _}|T], Acc) ->
- ssl_options_list(T, Acc);
-ssl_options_list([{renegotiate_at, _}|T], Acc) ->
- ssl_options_list(T, Acc);
-ssl_options_list([{max_fragment_length, _}|T], Acc) ->
- %% skip max_fragment_length from options since it is taken above from connection_states
- ssl_options_list(T, Acc);
-ssl_options_list([{ciphers = Key, Value}|T], Acc) ->
- ssl_options_list(T,
- [{Key, lists:map(
- fun(Suite) ->
- ssl_cipher_format:suite_bin_to_map(Suite)
- end, Value)}
- | Acc]);
-ssl_options_list([{Key, Value}|T], Acc) ->
- ssl_options_list(T, [{Key, Value} | Acc]).
-
-handle_active_option(false, connection = StateName, To, Reply, State) ->
- hibernate_after(StateName, State, [{reply, To, Reply}]);
-
-handle_active_option(_, connection = StateName, To, _Reply, #state{static_env = #static_env{role = Role},
- connection_env = #connection_env{terminated = true},
- user_data_buffer = {_,0,_}} = State) ->
- Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, all_data_deliverd),
- handle_normal_shutdown(Alert#alert{role = Role}, StateName,
- State#state{start_or_recv_from = To}),
- {stop,{shutdown, peer_close}, State};
-handle_active_option(_, connection = StateName0, To, Reply, #state{static_env = #static_env{protocol_cb = Connection},
- user_data_buffer = {_,0,_}} = State0) ->
- case Connection:next_event(StateName0, no_record, State0) of
- {next_state, StateName, State} ->
- hibernate_after(StateName, State, [{reply, To, Reply}]);
- {next_state, StateName, State, Actions} ->
- hibernate_after(StateName, State, [{reply, To, Reply} | Actions]);
- {stop, _, _} = Stop ->
- Stop
- end;
-handle_active_option(_, StateName, To, Reply, #state{user_data_buffer = {_,0,_}} = State) ->
- %% Active once already set
- {next_state, StateName, State, [{reply, To, Reply}]};
-
-%% user_data_buffer nonempty
-handle_active_option(_, StateName0, To, Reply,
- #state{static_env = #static_env{protocol_cb = Connection}} = State0) ->
- case read_application_data(<<>>, State0) of
- {stop, _, _} = Stop ->
- Stop;
- {Record, State1} ->
- %% Note: Renogotiation may cause StateName0 =/= StateName
- case Connection:next_event(StateName0, Record, State1) of
- {next_state, StateName, State} ->
- hibernate_after(StateName, State, [{reply, To, Reply}]);
- {next_state, StateName, State, Actions} ->
- hibernate_after(StateName, State, [{reply, To, Reply} | Actions]);
- {stop, _, _} = Stop ->
- Stop
- end
- end.
-
-
-%% Picks ClientData
-get_data(#socket_options{active=false}, undefined, _Bin) ->
- %% Recv timed out save buffer data until next recv
- passive;
-get_data(#socket_options{active=Active, packet=Raw}, BytesToRead, Bin)
- when Raw =:= raw; Raw =:= 0 -> %% Raw Mode
- case Bin of
- <<_/binary>> when Active =/= false orelse BytesToRead =:= 0 ->
- %% Active true or once, or passive mode recv(0)
- {ok, Bin, <<>>};
- <<Data:BytesToRead/binary, Rest/binary>> ->
- %% Passive Mode, recv(Bytes)
- {ok, Data, Rest};
- <<_/binary>> ->
- %% Passive Mode not enough data
- {more, BytesToRead}
- end;
-get_data(#socket_options{packet=Type, packet_size=Size}, _, Bin) ->
- PacketOpts = [{packet_size, Size}],
- decode_packet(Type, Bin, PacketOpts).
-
-decode_packet({http, headers}, Buffer, PacketOpts) ->
- decode_packet(httph, Buffer, PacketOpts);
-decode_packet({http_bin, headers}, Buffer, PacketOpts) ->
- decode_packet(httph_bin, Buffer, PacketOpts);
-decode_packet(Type, Buffer, PacketOpts) ->
- erlang:decode_packet(Type, Buffer, PacketOpts).
-
-%% Just like with gen_tcp sockets, an ssl socket that has been configured with
-%% {packet, http} (or {packet, http_bin}) will automatically switch to expect
-%% HTTP headers after it sees a HTTP Request or HTTP Response line. We
-%% represent the current state as follows:
-%% #socket_options.packet =:= http: Expect a HTTP Request/Response line
-%% #socket_options.packet =:= {http, headers}: Expect HTTP Headers
-%% Note that if the user has explicitly configured the socket to expect
-%% HTTP headers using the {packet, httph} option, we don't do any automatic
-%% switching of states.
-deliver_app_data(
- CPids, Transport, Socket,
- #socket_options{active=Active, packet=Type} = SOpts,
- Data, Pid, From, Trackers, Connection) ->
- %%
- send_or_reply(
- Active, Pid, From,
- format_reply(
- CPids, Transport, Socket, SOpts, Data, Trackers, Connection)),
- SO =
- case Data of
- {P, _, _, _}
- when ((P =:= http_request) or (P =:= http_response)),
- ((Type =:= http) or (Type =:= http_bin)) ->
- SOpts#socket_options{packet={Type, headers}};
- http_eoh when tuple_size(Type) =:= 2 ->
- %% End of headers - expect another Request/Response line
- {Type1, headers} = Type,
- SOpts#socket_options{packet=Type1};
- _ ->
- SOpts
- end,
- case Active of
- once ->
- SO#socket_options{active=false};
- 1 ->
- send_user(
- Pid,
- format_passive(
- CPids, Transport, Socket, Trackers, Connection)),
- SO#socket_options{active=false};
- N when is_integer(N) ->
- SO#socket_options{active=N - 1};
- _ ->
- SO
- end.
-
-format_reply(_, _, _,#socket_options{active = false, mode = Mode, packet = Packet,
- header = Header}, Data, _, _) ->
- {ok, do_format_reply(Mode, Packet, Header, Data)};
-format_reply(CPids, Transport, Socket, #socket_options{active = _, mode = Mode, packet = Packet,
- header = Header}, Data, Trackers, Connection) ->
- {ssl, Connection:socket(CPids, Transport, Socket, Trackers),
- do_format_reply(Mode, Packet, Header, Data)}.
-
-deliver_packet_error(CPids, Transport, Socket,
- SO= #socket_options{active = Active}, Data, Pid, From, Trackers, Connection) ->
- send_or_reply(Active, Pid, From, format_packet_error(CPids,
- Transport, Socket, SO, Data, Trackers, Connection)).
-
-format_packet_error(_, _, _,#socket_options{active = false, mode = Mode}, Data, _, _) ->
- {error, {invalid_packet, do_format_reply(Mode, raw, 0, Data)}};
-format_packet_error(CPids, Transport, Socket, #socket_options{active = _, mode = Mode},
- Data, Trackers, Connection) ->
- {ssl_error, Connection:socket(CPids, Transport, Socket, Trackers),
- {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}.
-
-do_format_reply(binary, _, N, Data) when N > 0 -> % Header mode
- header(N, Data);
-do_format_reply(binary, _, _, Data) ->
- Data;
-do_format_reply(list, Packet, _, Data)
- when Packet == http; Packet == {http, headers};
- Packet == http_bin; Packet == {http_bin, headers};
- Packet == httph; Packet == httph_bin ->
- Data;
-do_format_reply(list, _,_, Data) ->
- binary_to_list(Data).
-
-format_passive(CPids, Transport, Socket, Trackers, Connection) ->
- {ssl_passive, Connection:socket(CPids, Transport, Socket, Trackers)}.
-
-header(0, <<>>) ->
- <<>>;
-header(_, <<>>) ->
- [];
-header(0, Binary) ->
- Binary;
-header(N, Binary) ->
- <<?BYTE(ByteN), NewBinary/binary>> = Binary,
- [ByteN | header(N-1, NewBinary)].
-
-send_or_reply(false, _Pid, From, Data) when From =/= undefined ->
- gen_statem:reply(From, Data);
-send_or_reply(false, Pid, undefined, _) when is_pid(Pid) ->
- ok;
-send_or_reply(_, no_pid, _, _) ->
- ok;
-send_or_reply(_, Pid, _, Data) ->
- send_user(Pid, Data).
-
-send_user(Pid, Msg) ->
- Pid ! Msg,
- ok.
-
-alert_user(Pids, Transport, Trackers, Socket, connection, Opts, Pid, From, Alert, Role, StateName, Connection) ->
- alert_user(Pids, Transport, Trackers, Socket, Opts#socket_options.active, Pid, From, Alert, Role, StateName, Connection);
-alert_user(Pids, Transport, Trackers, Socket,_, _, _, From, Alert, Role, StateName, Connection) ->
- alert_user(Pids, Transport, Trackers, Socket, From, Alert, Role, StateName, Connection).
-
-alert_user(Pids, Transport, Trackers, Socket, From, Alert, Role, StateName, Connection) ->
- alert_user(Pids, Transport, Trackers, Socket, false, no_pid, From, Alert, Role, StateName, Connection).
-
-alert_user(_, _, _, _, false = Active, Pid, From, Alert, Role, StateName, Connection) when From =/= undefined ->
- %% If there is an outstanding ssl_accept | recv
- %% From will be defined and send_or_reply will
- %% send the appropriate error message.
- ReasonCode = ssl_alert:reason_code(Alert, Role, Connection:protocol_name(), StateName),
- send_or_reply(Active, Pid, From, {error, ReasonCode});
-alert_user(Pids, Transport, Trackers, Socket, Active, Pid, From, Alert, Role, StateName, Connection) ->
- case ssl_alert:reason_code(Alert, Role, Connection:protocol_name(), StateName) of
- closed ->
- send_or_reply(Active, Pid, From,
- {ssl_closed, Connection:socket(Pids, Transport, Socket, Trackers)});
- ReasonCode ->
- send_or_reply(Active, Pid, From,
- {ssl_error, Connection:socket(Pids, Transport, Socket, Trackers), ReasonCode})
- end.
-
-log_alert(Level, Role, ProtocolName, StateName, #alert{role = Role} = Alert) ->
- ssl_logger:log(notice, Level, #{protocol => ProtocolName,
- role => Role,
- statename => StateName,
- alert => Alert,
- alerter => own}, Alert#alert.where);
-log_alert(Level, Role, ProtocolName, StateName, Alert) ->
- ssl_logger:log(notice, Level, #{protocol => ProtocolName,
- role => Role,
- statename => StateName,
- alert => Alert,
- alerter => peer}, Alert#alert.where).
-
-maybe_invalidate_session({false, first}, server = Role, Host, Port, Session) ->
- invalidate_session(Role, Host, Port, Session);
-maybe_invalidate_session(_, _, _, _, _) ->
- ok.
-
-invalidate_session(client, Host, Port, Session) ->
- ssl_manager:invalidate_session(Host, Port, Session);
-invalidate_session(server, _, _, _) ->
- ok.
-
-handle_sni_extension(undefined, State) ->
- State;
-handle_sni_extension(#sni{hostname = Hostname}, State) ->
- case is_sni_value(Hostname) of
- true ->
- handle_sni_extension(Hostname, State);
- false ->
- ?ALERT_REC(?FATAL, ?UNRECOGNIZED_NAME, {sni_included_trailing_dot, Hostname})
- end;
-handle_sni_extension(Hostname, #state{static_env = #static_env{role = Role} = InitStatEnv0,
- handshake_env = HsEnv,
- connection_env = CEnv} = State0) ->
- NewOptions = update_ssl_options_from_sni(State0#state.ssl_options, Hostname),
- case NewOptions of
- undefined ->
- State0;
- _ ->
- {ok, #{cert_db_ref := Ref,
- cert_db_handle := CertDbHandle,
- fileref_db_handle := FileRefHandle,
- session_cache := CacheHandle,
- crl_db_info := CRLDbHandle,
- private_key := Key,
- dh_params := DHParams,
- own_certificate := OwnCert}} =
- ssl_config:init(NewOptions, Role),
- State0#state{
- session = State0#state.session#session{own_certificate = OwnCert},
- static_env = InitStatEnv0#static_env{
- file_ref_db = FileRefHandle,
- cert_db_ref = Ref,
- cert_db = CertDbHandle,
- crl_db = CRLDbHandle,
- session_cache = CacheHandle
- },
- connection_env = CEnv#connection_env{private_key = Key},
- ssl_options = NewOptions,
- handshake_env = HsEnv#handshake_env{sni_hostname = Hostname,
- diffie_hellman_params = DHParams}
- }
- end.
-
-update_ssl_options_from_sni(#{sni_fun := SNIFun,
- sni_hosts := SNIHosts} = OrigSSLOptions, SNIHostname) ->
- SSLOption =
- case SNIFun of
- undefined ->
- proplists:get_value(SNIHostname,
- SNIHosts);
- SNIFun ->
- SNIFun(SNIHostname)
- end,
- case SSLOption of
- undefined ->
- undefined;
- _ ->
- ssl:handle_options(SSLOption, server, OrigSSLOptions)
- end.
-
-new_emulated([], EmOpts) ->
- EmOpts;
-new_emulated(NewEmOpts, _) ->
- NewEmOpts.
-
-no_records(Extensions) ->
- maps:map(fun(_, Value) ->
- ssl_handshake:extension_value(Value)
- end, Extensions).
-
-is_sni_value(Hostname) ->
- case hd(lists:reverse(Hostname)) of
- $. ->
- false;
- _ ->
- true
- end.
-
-ensure_tls({254, _} = Version) ->
- dtls_v1:corresponding_tls_version(Version);
-ensure_tls(Version) ->
- Version.
-
-ocsp_info(#{ocsp_expect := stapled,
- ocsp_response := CertStatus} = OcspState,
- #{ocsp_responder_certs := OcspResponderCerts}, PeerCert) ->
- #{cert_ext => #{public_key:pkix_subject_id(PeerCert) => [CertStatus]},
- ocsp_responder_certs => OcspResponderCerts,
- ocsp_state => OcspState
- };
-ocsp_info(#{ocsp_expect := no_staple} = OcspState, _, PeerCert) ->
- #{cert_ext => #{public_key:pkix_subject_id(PeerCert) => []},
- ocsp_responder_certs => [],
- ocsp_state => OcspState
- }.
diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl
index b8a17f0409..371599bbe8 100644
--- a/lib/ssl/src/ssl_connection.hrl
+++ b/lib/ssl/src/ssl_connection.hrl
@@ -36,7 +36,7 @@
-record(static_env, {
role :: client | server,
transport_cb :: atom(), % callback module
- protocol_cb :: tls_connection | dtls_connection,
+ protocol_cb :: tls_gen_connection | dtls_gen_connection,
data_tag :: atom(), % ex tcp.
close_tag :: atom(), % ex tcp_closed
error_tag :: atom(), % ex tcp_error
@@ -63,6 +63,7 @@
renegotiation :: undefined | {boolean(), From::term() | internal | peer},
resumption = false :: boolean(), %% TLS 1.3
change_cipher_spec_sent = false :: boolean(), %% TLS 1.3
+ sni_guided_cert_selection = false :: boolean(), %% TLS 1.3
allow_renegotiate = true ::boolean(),
%% Ext handling
hello, %%:: #client_hello{} | #server_hello{}
diff --git a/lib/ssl/src/ssl_dist_connection_sup.erl b/lib/ssl/src/ssl_dist_connection_sup.erl
index 28c8692ca5..441a7577be 100644
--- a/lib/ssl/src/ssl_dist_connection_sup.erl
+++ b/lib/ssl/src/ssl_dist_connection_sup.erl
@@ -42,48 +42,20 @@ start_link() ->
%%%=========================================================================
%%% Supervisor callback
%%%=========================================================================
-
init([]) ->
-
- TLSConnetionManager = tls_connection_manager_child_spec(),
- %% Handles emulated options so that they inherited by the accept
- %% socket, even when setopts is performed on the listen socket
- ListenOptionsTracker = listen_options_tracker_child_spec(),
- Pre_1_3SessionTracker = ssl_server_session_child_spec(),
-
- {ok, {{one_for_one, 10, 3600}, [TLSConnetionManager,
- ListenOptionsTracker,
- Pre_1_3SessionTracker
- ]}}.
+ TLSSup = tls_sup_child_spec(),
+ {ok, {{one_for_one, 10, 3600}, [TLSSup]}}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-tls_connection_manager_child_spec() ->
- Name = dist_tls_connection,
- StartFunc = {tls_connection_sup, start_link_dist, []},
+tls_sup_child_spec() ->
+ Name = dist_tls_sup,
+ StartFunc = {tls_dist_sup, start_link, []},
Restart = permanent,
Shutdown = 4000,
- Modules = [tls_connection_sup],
- Type = supervisor,
- {Name, StartFunc, Restart, Shutdown, Type, Modules}.
-
-listen_options_tracker_child_spec() ->
- Name = dist_tls_socket,
- StartFunc = {ssl_listen_tracker_sup, start_link_dist, []},
- Restart = permanent,
- Shutdown = 4000,
- Modules = [tls_socket],
- Type = supervisor,
- {Name, StartFunc, Restart, Shutdown, Type, Modules}.
-
-ssl_server_session_child_spec() ->
- Name = dist_ssl_server_session_cache_sup,
- StartFunc = {ssl_server_session_cache_sup, start_link_dist, []},
- Restart = permanent,
- Shutdown = 4000,
- Modules = [ssl_server_session_cache_sup],
+ Modules = [tls_dist_sup],
Type = supervisor,
{Name, StartFunc, Restart, Shutdown, Type, Modules}.
diff --git a/lib/ssl/src/ssl_dist_sup.erl b/lib/ssl/src/ssl_dist_sup.erl
index bea67935d8..ae0887c3d9 100644
--- a/lib/ssl/src/ssl_dist_sup.erl
+++ b/lib/ssl/src/ssl_dist_sup.erl
@@ -70,16 +70,16 @@ ssl_admin_child_spec() ->
StartFunc = {ssl_dist_admin_sup, start_link , []},
Restart = permanent,
Shutdown = 4000,
- Modules = [ssl_admin_sup],
+ Modules = [ssl_dist_admin_sup],
Type = supervisor,
{Name, StartFunc, Restart, Shutdown, Type, Modules}.
ssl_connection_sup() ->
- Name = ssl_dist_connection_sup,
- StartFunc = {ssl_dist_connection_sup, start_link, []},
+ Name = tls_dist_sup,
+ StartFunc = {tls_dist_sup, start_link, []},
Restart = permanent,
Shutdown = 4000,
- Modules = [ssl_connection_sup],
+ Modules = [tls_dist_sup],
Type = supervisor,
{Name, StartFunc, Restart, Shutdown, Type, Modules}.
diff --git a/lib/ssl/src/ssl_gen_statem.erl b/lib/ssl/src/ssl_gen_statem.erl
new file mode 100644
index 0000000000..aa2a5541a1
--- /dev/null
+++ b/lib/ssl/src/ssl_gen_statem.erl
@@ -0,0 +1,2011 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Provid help function to handle generic parts of TLS
+%% connection fsms
+%%----------------------------------------------------------------------
+
+-module(ssl_gen_statem).
+
+-include_lib("kernel/include/logger.hrl").
+
+-include("ssl_api.hrl").
+-include("ssl_internal.hrl").
+-include("ssl_connection.hrl").
+-include("ssl_alert.hrl").
+-include("tls_handshake.hrl").
+
+%% Initial Erlang process setup
+-export([start_link/7,
+ start_link/8,
+ init/1]).
+
+%% TLS connection setup
+-export([ssl_config/3,
+ connect/8,
+ handshake/7,
+ handshake/2,
+ handshake/3,
+ handshake_continue/3,
+ handshake_cancel/1,
+ handle_sni_extension/2,
+ socket_control/4,
+ socket_control/5,
+ prepare_connection/2]).
+
+%% User Events
+-export([send/2,
+ recv/3,
+ close/2,
+ shutdown/2,
+ new_user/2,
+ get_opts/2,
+ set_opts/2,
+ peer_certificate/1,
+ negotiated_protocol/1,
+ connection_information/2
+ ]).
+
+%% Erlang Distribution export
+-export([dist_handshake_complete/2]).
+
+%% Generic fsm states
+-export([initial_hello/3,
+ config_error/3,
+ connection/3]).
+
+-export([call/2,
+ handle_common_event/4,
+ handle_call/4,
+ handle_info/3
+ ]).
+
+-export([hibernate_after/3]).
+
+%% Data handling
+-export([read_application_data/2]).
+
+%% Alert and close handling
+-export([send_alert/3,
+ handle_own_alert/4,
+ handle_alert/3,
+ handle_normal_shutdown/3,
+ handle_trusted_certs_db/1,
+ maybe_invalidate_session/6,
+ maybe_invalidate_session/5,
+ terminate/3]).
+
+%% Log handling
+-export([format_status/2]).
+
+%%--------------------------------------------------------------------
+%%% Initial Erlang process setup
+%%--------------------------------------------------------------------
+%%--------------------------------------------------------------------
+-spec start_link(client| server, pid(), ssl:host(), inet:port_number(), port(), list(), pid(), tuple()) ->
+ {ok, pid()} | ignore | {error, reason()}.
+%%
+%% Description: Creates a process which calls Module:init/1 to
+%% choose appropriat gen_statem and initialize.
+%%--------------------------------------------------------------------
+start_link(Role, Sender, Host, Port, Socket, Options, User, CbInfo) ->
+ {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Sender, Host, Port, Socket, Options, User, CbInfo]])}.
+
+%%--------------------------------------------------------------------
+-spec start_link(atom(), ssl:host(), inet:port_number(), port(), list(), pid(), tuple()) ->
+ {ok, pid()} | ignore | {error, reason()}.
+%%
+%% Description: Creates a gen_statem process which calls Module:init/1 to
+%% initialize.
+%%--------------------------------------------------------------------
+start_link(Role, Host, Port, Socket, Options, User, CbInfo) ->
+ {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Host, Port, Socket, Options, User, CbInfo]])}.
+
+
+%%--------------------------------------------------------------------
+-spec init(list()) -> no_return().
+%% Description: Initialization
+%%--------------------------------------------------------------------
+init([_Role, Sender, _Host, _Port, _Socket, {#{erl_dist := ErlDist} = TLSOpts, _, _}, _User, _CbInfo] = InitArgs) ->
+ process_flag(trap_exit, true),
+ link(Sender),
+ case ErlDist of
+ true ->
+ process_flag(priority, max);
+ _ ->
+ ok
+ end,
+ ConnectionFsm = tls_connection_fsm(TLSOpts),
+ ConnectionFsm:init(InitArgs);
+init([_Role, _Host, _Port, _Socket, {TLSOpts, _, _}, _User, _CbInfo] = InitArgs) ->
+ process_flag(trap_exit, true),
+ ConnectionFsm = dtls_connection_fsm(TLSOpts),
+ ConnectionFsm:init(InitArgs).
+
+%%====================================================================
+%% TLS connection setup
+%%====================================================================
+
+%%--------------------------------------------------------------------
+-spec ssl_config(ssl_options(), client | server, #state{}) -> #state{}.
+%%--------------------------------------------------------------------
+ssl_config(Opts, Role, #state{static_env = InitStatEnv0,
+ handshake_env = HsEnv,
+ connection_env = CEnv} = State0) ->
+ {ok, #{cert_db_ref := Ref,
+ cert_db_handle := CertDbHandle,
+ fileref_db_handle := FileRefHandle,
+ session_cache := CacheHandle,
+ crl_db_info := CRLDbHandle,
+ private_key := Key,
+ dh_params := DHParams,
+ own_certificates := OwnCerts}} =
+ ssl_config:init(Opts, Role),
+ TimeStamp = erlang:monotonic_time(),
+ Session = State0#state.session,
+
+ State0#state{session = Session#session{own_certificates = OwnCerts,
+ time_stamp = TimeStamp},
+ static_env = InitStatEnv0#static_env{
+ file_ref_db = FileRefHandle,
+ cert_db_ref = Ref,
+ cert_db = CertDbHandle,
+ crl_db = CRLDbHandle,
+ session_cache = CacheHandle
+ },
+ handshake_env = HsEnv#handshake_env{diffie_hellman_params = DHParams},
+ connection_env = CEnv#connection_env{private_key = Key},
+ ssl_options = Opts}.
+
+%%--------------------------------------------------------------------
+-spec connect(tls_gen_connection | dtls_gen_connection,
+ ssl:host(), inet:port_number(),
+ port() | {tuple(), port()}, %% TLS | DTLS
+ {ssl_options(), #socket_options{},
+ %% Tracker only needed on server side
+ undefined},
+ pid(), tuple(), timeout()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
+%%
+%% Description: Connect to an ssl server.
+%%--------------------------------------------------------------------
+connect(Connection, Host, Port, Socket, Options, User, CbInfo, Timeout) ->
+ try Connection:start_fsm(client, Host, Port, Socket, Options, User, CbInfo,
+ Timeout)
+ catch
+ exit:{noproc, _} ->
+ {error, ssl_not_started}
+ end.
+%%--------------------------------------------------------------------
+-spec handshake(tls_gen_connection | dtls_gen_connection,
+ inet:port_number(), port(),
+ {ssl_options(), #socket_options{}, list()},
+ pid(), tuple(), timeout()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
+%%
+%% Description: Performs accept on an ssl listen socket. e.i. performs
+%% ssl handshake.
+%%--------------------------------------------------------------------
+handshake(Connection, Port, Socket, Opts, User, CbInfo, Timeout) ->
+ try Connection:start_fsm(server, "localhost", Port, Socket, Opts, User,
+ CbInfo, Timeout)
+ catch
+ exit:{noproc, _} ->
+ {error, ssl_not_started}
+ end.
+
+%%--------------------------------------------------------------------
+-spec handshake(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} |
+ {ok, #sslsocket{}, map()}| {error, reason()}.
+%%
+%% Description: Starts ssl handshake.
+%%--------------------------------------------------------------------
+handshake(#sslsocket{pid = [Pid|_]} = Socket, Timeout) ->
+ case call(Pid, {start, Timeout}) of
+ connected ->
+ {ok, Socket};
+ {ok, Ext} ->
+ {ok, Socket, no_records(Ext)};
+ Error ->
+ Error
+ end.
+
+%%--------------------------------------------------------------------
+-spec handshake(#sslsocket{}, {ssl_options(),#socket_options{}}, timeout()) ->
+ {ok, #sslsocket{}} | {ok, #sslsocket{}, map()} | {error, reason()}.
+%%
+%% Description: Starts ssl handshake with some new options
+%%--------------------------------------------------------------------
+handshake(#sslsocket{pid = [Pid|_]} = Socket, SslOptions, Timeout) ->
+ case call(Pid, {start, SslOptions, Timeout}) of
+ connected ->
+ {ok, Socket};
+ Error ->
+ Error
+ end.
+
+%%--------------------------------------------------------------------
+-spec handshake_continue(#sslsocket{}, [ssl:tls_server_option()],
+ timeout()) -> {ok, #sslsocket{}}| {error, reason()}.
+%%
+%% Description: Continues handshake with new options
+%%--------------------------------------------------------------------
+handshake_continue(#sslsocket{pid = [Pid|_]} = Socket, SslOptions, Timeout) ->
+ case call(Pid, {handshake_continue, SslOptions, Timeout}) of
+ connected ->
+ {ok, Socket};
+ Error ->
+ Error
+ end.
+%%--------------------------------------------------------------------
+-spec handshake_cancel(#sslsocket{}) -> ok | {error, reason()}.
+%%
+%% Description: Cancels connection
+%%--------------------------------------------------------------------
+handshake_cancel(#sslsocket{pid = [Pid|_]}) ->
+ case call(Pid, cancel) of
+ closed ->
+ ok;
+ Error ->
+ Error
+ end.
+%--------------------------------------------------------------------
+-spec socket_control(tls_gen_connection | dtls_gen_connection, port(), [pid()], atom()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
+%%
+%% Description: Set the ssl process to own the accept socket
+%%--------------------------------------------------------------------
+socket_control(Connection, Socket, Pid, Transport) ->
+ socket_control(Connection, Socket, Pid, Transport, undefined).
+
+%--------------------------------------------------------------------
+-spec socket_control(tls_gen_connection | dtls_gen_connection, port(), [pid()], atom(), [pid()] | atom()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
+%%--------------------------------------------------------------------
+socket_control(dtls_gen_connection = Connection, Socket, Pids, Transport, udp_listener) ->
+ %% dtls listener process must have the socket control
+ {ok, Connection:socket(Pids, Transport, Socket, undefined)};
+
+socket_control(tls_gen_connection = Connection, Socket, [Pid|_] = Pids, Transport, Trackers) ->
+ case Transport:controlling_process(Socket, Pid) of
+ ok ->
+ {ok, Connection:socket(Pids, Transport, Socket, Trackers)};
+ {error, Reason} ->
+ {error, Reason}
+ end;
+socket_control(dtls_gen_connection = Connection, {PeerAddrPort, Socket}, [Pid|_] = Pids, Transport, Trackers) ->
+ case Transport:controlling_process(Socket, Pid) of
+ ok ->
+ {ok, Connection:socket(Pids, Transport, {PeerAddrPort, Socket}, Trackers)};
+ {error, Reason} ->
+ {error, Reason}
+ end.
+
+prepare_connection(#state{handshake_env = #handshake_env{renegotiation = Renegotiate},
+ start_or_recv_from = RecvFrom} = State0, Connection)
+ when Renegotiate =/= {false, first},
+ RecvFrom =/= undefined ->
+ State = Connection:reinit(State0),
+ {no_record, ack_connection(State)};
+prepare_connection(State0, Connection) ->
+ State = Connection:reinit(State0),
+ {no_record, ack_connection(State)}.
+
+%%====================================================================
+%% User events
+%%====================================================================
+
+%%--------------------------------------------------------------------
+-spec send(pid(), iodata()) -> ok | {error, reason()}.
+%%
+%% Description: Sends data over the ssl connection
+%%--------------------------------------------------------------------
+send(Pid, Data) ->
+ call(Pid, {application_data,
+ %% iolist_to_iovec should really
+ %% be called iodata_to_iovec()
+ erlang:iolist_to_iovec(Data)}).
+
+%%--------------------------------------------------------------------
+-spec recv(pid(), integer(), timeout()) ->
+ {ok, binary() | list()} | {error, reason()}.
+%%
+%% Description: Receives data when active = false
+%%--------------------------------------------------------------------
+recv(Pid, Length, Timeout) ->
+ call(Pid, {recv, Length, Timeout}).
+
+%%--------------------------------------------------------------------
+-spec connection_information(pid(), boolean()) -> {ok, list()} | {error, reason()}.
+%%
+%% Description: Get connection information
+%%--------------------------------------------------------------------
+connection_information(Pid, IncludeSecrityInfo) when is_pid(Pid) ->
+ case call(Pid, {connection_information, IncludeSecrityInfo}) of
+ {ok, Info} when IncludeSecrityInfo == true ->
+ {ok, maybe_add_keylog(Info)};
+ Other ->
+ Other
+ end.
+
+%%--------------------------------------------------------------------
+-spec close(pid(), {close, Timeout::integer() |
+ {NewController::pid(), Timeout::integer()}}) ->
+ ok | {ok, port()} | {error, reason()}.
+%%
+%% Description: Close an ssl connection
+%%--------------------------------------------------------------------
+close(ConnectionPid, How) ->
+ case call(ConnectionPid, How) of
+ {error, closed} ->
+ ok;
+ Other ->
+ Other
+ end.
+%%--------------------------------------------------------------------
+-spec shutdown(pid(), atom()) -> ok | {error, reason()}.
+%%
+%% Description: Same as gen_tcp:shutdown/2
+%%--------------------------------------------------------------------
+shutdown(ConnectionPid, How) ->
+ call(ConnectionPid, {shutdown, How}).
+
+%%--------------------------------------------------------------------
+-spec new_user(pid(), pid()) -> ok | {error, reason()}.
+%%
+%% Description: Changes process that receives the messages when active = true
+%% or once.
+%%--------------------------------------------------------------------
+new_user(ConnectionPid, User) ->
+ call(ConnectionPid, {new_user, User}).
+
+%%--------------------------------------------------------------------
+-spec get_opts(pid(), list()) -> {ok, list()} | {error, reason()}.
+%%
+%% Description: Same as inet:getopts/2
+%%--------------------------------------------------------------------
+get_opts(ConnectionPid, OptTags) ->
+ call(ConnectionPid, {get_opts, OptTags}).
+%%--------------------------------------------------------------------
+-spec set_opts(pid(), list()) -> ok | {error, reason()}.
+%%
+%% Description: Same as inet:setopts/2
+%%--------------------------------------------------------------------
+set_opts(ConnectionPid, Options) ->
+ call(ConnectionPid, {set_opts, Options}).
+
+%%--------------------------------------------------------------------
+-spec peer_certificate(pid()) -> {ok, binary()| undefined} | {error, reason()}.
+%%
+%% Description: Returns the peer cert
+%%--------------------------------------------------------------------
+peer_certificate(ConnectionPid) ->
+ call(ConnectionPid, peer_certificate).
+
+%%--------------------------------------------------------------------
+-spec negotiated_protocol(pid()) -> {ok, binary()} | {error, reason()}.
+%%
+%% Description: Returns the negotiated protocol
+%%--------------------------------------------------------------------
+negotiated_protocol(ConnectionPid) ->
+ call(ConnectionPid, negotiated_protocol).
+
+dist_handshake_complete(ConnectionPid, DHandle) ->
+ gen_statem:cast(ConnectionPid, {dist_handshake_complete, DHandle}).
+
+handle_sni_extension(undefined, State) ->
+ {ok, State};
+handle_sni_extension(#sni{hostname = Hostname}, State0) ->
+ case check_hostname(State0, Hostname) of
+ valid ->
+ State1 = handle_sni_hostname(Hostname, State0),
+ State = set_sni_guided_cert_selection(State1, true),
+ {ok, State};
+ unrecognized_name ->
+ {ok, handle_sni_hostname(Hostname, State0)};
+ #alert{} = Alert ->
+ {error, Alert}
+ end.
+
+%%====================================================================
+%% Generic states
+%%====================================================================
+%%--------------------------------------------------------------------
+-spec initial_hello(gen_statem:event_type(),
+ {start, timeout()} | {start, {list(), list()}, timeout()}| term(),
+ #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+initial_hello({call, From}, {start, Timeout},
+ #state{static_env = #static_env{role = client = Role,
+ host = Host,
+ port = Port,
+ protocol_cb = Connection,
+ transport_cb = Transport,
+ socket = Socket},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _},
+ ocsp_stapling_state = OcspState0} = HsEnv,
+ connection_env = CEnv,
+ ssl_options = #{log_level := LogLevel,
+ %% Use highest version in initial ClientHello.
+ %% Versions is a descending list of supported versions.
+ versions := [HelloVersion|_] = Versions,
+ session_tickets := SessionTickets,
+ ocsp_stapling := OcspStaplingOpt,
+ ocsp_nonce := OcspNonceOpt} = SslOpts,
+ session = Session,
+ connection_states = ConnectionStates0
+ } = State0) ->
+
+ KeyShare = maybe_generate_client_shares(SslOpts),
+ %% Update UseTicket in case of automatic session resumption
+ {UseTicket, State1} = tls_handshake_1_3:maybe_automatic_session_resumption(State0),
+ TicketData = tls_handshake_1_3:get_ticket_data(self(), SessionTickets, UseTicket),
+ OcspNonce = tls_handshake:ocsp_nonce(OcspNonceOpt, OcspStaplingOpt),
+ Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
+ Session#session.session_id,
+ Renegotiation,
+ Session#session.own_certificates,
+ KeyShare,
+ TicketData,
+ OcspNonce),
+
+ Handshake0 = ssl_handshake:init_handshake_history(),
+
+ %% Update pre_shared_key extension with binders (TLS 1.3)
+ Hello1 = tls_handshake_1_3:maybe_add_binders(Hello, TicketData, HelloVersion),
+
+ MaxFragEnum = maps:get(max_frag_enum, Hello1#client_hello.extensions, undefined),
+ ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0),
+
+ {BinMsg, ConnectionStates, Handshake} =
+ Connection:encode_handshake(Hello1, HelloVersion, ConnectionStates1, Handshake0),
+
+ tls_socket:send(Transport, Socket, BinMsg),
+ ssl_logger:debug(LogLevel, outbound, 'handshake', Hello1),
+ ssl_logger:debug(LogLevel, outbound, 'record', BinMsg),
+
+ %% RequestedVersion is used as the legacy record protocol version and shall be
+ %% {3,3} in case of TLS 1.2 and higher. In all other cases it defaults to the
+ %% lowest supported protocol version.
+ %%
+ %% negotiated_version is also used by the TLS 1.3 state machine and is set after
+ %% ServerHello is processed.
+ RequestedVersion = tls_record:hello_version(Versions),
+ State = State1#state{connection_states = ConnectionStates,
+ connection_env = CEnv#connection_env{
+ negotiated_version = RequestedVersion},
+ session = Session,
+ handshake_env = HsEnv#handshake_env{
+ tls_handshake_history = Handshake,
+ ocsp_stapling_state = OcspState0#{ocsp_nonce => OcspNonce}},
+ start_or_recv_from = From,
+ key_share = KeyShare},
+ NextState = next_statem_state(Versions, Role),
+ Connection:next_event(NextState, no_record, State,
+ [{{timeout, handshake}, Timeout, close}]);
+initial_hello({call, From}, {start, Timeout}, #state{static_env = #static_env{role = Role,
+ protocol_cb = Connection},
+ ssl_options = #{versions := Versions}} = State0) ->
+
+ NextState = next_statem_state(Versions, Role),
+ Connection:next_event(NextState, no_record, State0#state{start_or_recv_from = From},
+ [{{timeout, handshake}, Timeout, close}]);
+
+initial_hello({call, From}, {start, {Opts, EmOpts}, Timeout},
+ #state{static_env = #static_env{role = Role},
+ ssl_options = OrigSSLOptions,
+ socket_options = SockOpts} = State0) ->
+ try
+ SslOpts = ssl:handle_options(Opts, Role, OrigSSLOptions),
+ State = ssl_config(SslOpts, Role, State0),
+ initial_hello({call, From}, {start, Timeout},
+ State#state{ssl_options = SslOpts,
+ socket_options = new_emulated(EmOpts, SockOpts)})
+ catch throw:Error ->
+ {stop_and_reply, {shutdown, normal}, {reply, From, {error, Error}}, State0}
+ end;
+initial_hello({call, From}, {new_user, _} = Msg, State) ->
+ handle_call(Msg, From, ?FUNCTION_NAME, State);
+initial_hello({call, From}, _Msg, _State) ->
+ {keep_state_and_data, [{reply, From, {error, notsup_on_transport_accept_socket}}]};
+initial_hello(_Type, _Event, _State) ->
+ {keep_state_and_data, [postpone]}.
+
+%%--------------------------------------------------------------------
+-spec config_error(gen_statem:event_type(),
+ {start, timeout()} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+config_error({call, From}, {start, _Timeout},
+ #state{protocol_specific = #{error := Error}} = State) ->
+ {stop_and_reply, {shutdown, normal},
+ [{reply, From, {error, Error}}], State};
+config_error({call, From}, {close, _}, State) ->
+ {stop_and_reply, {shutdown, normal}, {reply, From, ok}, State};
+config_error({call, From}, _Msg, State) ->
+ {next_state, ?FUNCTION_NAME, State, [{reply, From, {error, closed}}]};
+config_error(_Type, _Event, _State) ->
+ {keep_state_and_data, [postpone]}.
+
+%%--------------------------------------------------------------------
+-spec connection(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+connection({call, RecvFrom}, {recv, N, Timeout},
+ #state{static_env = #static_env{protocol_cb = Connection},
+ socket_options =
+ #socket_options{active = false}} = State0) ->
+ passive_receive(State0#state{bytes_to_read = N,
+ start_or_recv_from = RecvFrom}, ?FUNCTION_NAME, Connection,
+ [{{timeout, recv}, Timeout, timeout}]);
+connection({call, From}, peer_certificate,
+ #state{session = #session{peer_certificate = Cert}} = State) ->
+ hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Cert}}]);
+connection({call, From}, {connection_information, true}, State) ->
+ Info = connection_info(State) ++ security_info(State),
+ hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Info}}]);
+connection({call, From}, {connection_information, false}, State) ->
+ Info = connection_info(State),
+ hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Info}}]);
+connection({call, From}, negotiated_protocol,
+ #state{handshake_env = #handshake_env{alpn = undefined,
+ negotiated_protocol = undefined}} = State) ->
+ hibernate_after(?FUNCTION_NAME, State, [{reply, From, {error, protocol_not_negotiated}}]);
+connection({call, From}, negotiated_protocol,
+ #state{handshake_env = #handshake_env{alpn = undefined,
+ negotiated_protocol = SelectedProtocol}} = State) ->
+ hibernate_after(?FUNCTION_NAME, State,
+ [{reply, From, {ok, SelectedProtocol}}]);
+connection({call, From}, negotiated_protocol,
+ #state{handshake_env = #handshake_env{alpn = SelectedProtocol,
+ negotiated_protocol = undefined}} = State) ->
+ hibernate_after(?FUNCTION_NAME, State,
+ [{reply, From, {ok, SelectedProtocol}}]);
+connection({call, From},
+ {close, {Pid, _Timeout}},
+ #state{connection_env = #connection_env{terminated = closed} = CEnv,
+ protocol_specific = PS} = State) ->
+ {next_state, downgrade, State#state{connection_env =
+ CEnv#connection_env{terminated = true,
+ downgrade = {Pid, From}},
+ protocol_specific = PS#{active_n_toggle => true,
+ active_n => 1}
+ },
+ [{next_event, internal, ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY)}]};
+connection({call, From},
+ {close,{Pid, Timeout}},
+ #state{connection_states = ConnectionStates,
+ static_env = #static_env{protocol_cb = Connection},
+ protocol_specific = #{sender := Sender} = PS,
+ connection_env = CEnv
+ } = State0) ->
+ case tls_sender:downgrade(Sender, Timeout) of
+ {ok, Write} ->
+ %% User downgrades connection
+ %% When downgrading an TLS connection to a transport connection
+ %% we must recive the close alert from the peer before releasing the
+ %% transport socket.
+ State = Connection:send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
+ State0#state{connection_states =
+ ConnectionStates#{current_write => Write}}),
+ {next_state, downgrade, State#state{connection_env =
+ CEnv#connection_env{downgrade = {Pid, From},
+ terminated = true},
+ protocol_specific = PS#{active_n_toggle => true,
+ active_n => 1}
+ },
+ [{timeout, Timeout, downgrade}]};
+ {error, timeout} ->
+ {stop_and_reply, {shutdown, downgrade_fail}, [{reply, From, {error, timeout}}]}
+ end;
+connection({call, From}, Msg, State) ->
+ handle_call(Msg, From, ?FUNCTION_NAME, State);
+connection(cast, {dist_handshake_complete, DHandle},
+ #state{ssl_options = #{erl_dist := true},
+ static_env = #static_env{protocol_cb = Connection},
+ connection_env = CEnv,
+ socket_options = SockOpts} = State0) ->
+ process_flag(priority, normal),
+ State1 =
+ State0#state{
+ socket_options = SockOpts#socket_options{active = true},
+ connection_env = CEnv#connection_env{erl_dist_handle = DHandle},
+ bytes_to_read = undefined},
+ {Record, State} = read_application_data(<<>>, State1),
+ Connection:next_event(connection, Record, State);
+connection(info, Msg, #state{static_env = #static_env{protocol_cb = Connection}} = State) ->
+ Connection:handle_info(Msg, ?FUNCTION_NAME, State);
+connection(internal, {recv, RecvFrom}, #state{start_or_recv_from = RecvFrom,
+ static_env = #static_env{protocol_cb = Connection}} = State) ->
+ passive_receive(State, ?FUNCTION_NAME, Connection, []);
+connection(Type, Msg, State) ->
+ handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+
+%%====================================================================
+%% Event/Msg handling
+%%====================================================================
+handle_common_event(internal, {handshake, {Handshake, Raw}}, StateName,
+ #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv,
+ connection_env = #connection_env{negotiated_version = _Version}} = State0) ->
+ Hist = ssl_handshake:update_handshake_history(Hist0, Raw),
+ {next_state, StateName,
+ State0#state{handshake_env =
+ HsEnv#handshake_env{tls_handshake_history = Hist}},
+ [{next_event, internal, Handshake}]};
+handle_common_event(internal, {protocol_record, TLSorDTLSRecord}, StateName,
+ #state{static_env = #static_env{protocol_cb = Connection}} = State) ->
+ Connection:handle_protocol_record(TLSorDTLSRecord, StateName, State);
+handle_common_event(timeout, hibernate, _, _) ->
+ {keep_state_and_data, [hibernate]};
+handle_common_event(internal, #change_cipher_spec{type = <<1>>}, StateName,
+ #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
+ handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), Version, StateName, State);
+handle_common_event({timeout, handshake}, close, _StateName, #state{start_or_recv_from = StartFrom} = State) ->
+ {stop_and_reply,
+ {shutdown, user_timeout},
+ {reply, StartFrom, {error, timeout}}, State#state{start_or_recv_from = undefined}};
+handle_common_event({timeout, recv}, timeout, StateName, #state{start_or_recv_from = RecvFrom} = State) ->
+ {next_state, StateName, State#state{start_or_recv_from = undefined,
+ bytes_to_read = undefined}, [{reply, RecvFrom, {error, timeout}}]};
+handle_common_event(internal, {recv, RecvFrom}, StateName, #state{start_or_recv_from = RecvFrom}) when
+ StateName =/= connection ->
+ {keep_state_and_data, [postpone]};
+handle_common_event(Type, Msg, StateName, #state{connection_env =
+ #connection_env{negotiated_version = Version}} = State) ->
+ Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, {unexpected_msg, {Type, Msg}}),
+ handle_own_alert(Alert, Version, StateName, State).
+
+handle_call({application_data, _Data}, _, _, _) ->
+ %% In renegotiation priorities handshake, send data when handshake is finished
+ {keep_state_and_data, [postpone]};
+handle_call({close, _} = Close, From, StateName, #state{connection_env = CEnv} = State) ->
+ %% Run terminate before returning so that the reuseaddr
+ %% inet-option works properly
+ Result = terminate(Close, StateName, State),
+ {stop_and_reply,
+ {shutdown, normal},
+ {reply, From, Result}, State#state{connection_env = CEnv#connection_env{terminated = true}}};
+handle_call({shutdown, read_write = How}, From, StateName,
+ #state{static_env = #static_env{transport_cb = Transport,
+ socket = Socket},
+ connection_env = CEnv} = State) ->
+ try send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
+ StateName, State) of
+ _ ->
+ case Transport:shutdown(Socket, How) of
+ ok ->
+ {next_state, StateName, State#state{connection_env =
+ CEnv#connection_env{terminated = true}},
+ [{reply, From, ok}]};
+ Error ->
+ {stop_and_reply, {shutdown, normal}, {reply, From, Error},
+ State#state{connection_env = CEnv#connection_env{terminated = true}}}
+ end
+ catch
+ throw:Return ->
+ Return
+ end;
+handle_call({shutdown, How0}, From, StateName,
+ #state{static_env = #static_env{transport_cb = Transport,
+ socket = Socket}} = State) ->
+ case Transport:shutdown(Socket, How0) of
+ ok ->
+ {next_state, StateName, State, [{reply, From, ok}]};
+ Error ->
+ {stop_and_reply, {shutdown, normal}, {reply, From, Error}, State}
+ end;
+handle_call({recv, _N, _Timeout}, From, _,
+ #state{socket_options =
+ #socket_options{active = Active}}) when Active =/= false ->
+ {keep_state_and_data, [{reply, From, {error, einval}}]};
+handle_call({recv, N, Timeout}, RecvFrom, StateName, State) ->
+ %% Doing renegotiate wait with handling request until renegotiate is
+ %% finished.
+ {next_state, StateName, State#state{bytes_to_read = N, start_or_recv_from = RecvFrom},
+ [{next_event, internal, {recv, RecvFrom}} , {{timeout, recv}, Timeout, timeout}]};
+handle_call({new_user, User}, From, StateName,
+ State = #state{connection_env = #connection_env{user_application = {OldMon, _}} = CEnv}) ->
+ NewMon = erlang:monitor(process, User),
+ erlang:demonitor(OldMon, [flush]),
+ {next_state, StateName, State#state{connection_env = CEnv#connection_env{user_application = {NewMon, User}}},
+ [{reply, From, ok}]};
+handle_call({get_opts, OptTags}, From, _,
+ #state{static_env = #static_env{protocol_cb = Connection,
+ socket = Socket,
+ transport_cb = Transport},
+ socket_options = SockOpts}) ->
+ OptsReply = get_socket_opts(Connection, Transport, Socket, OptTags, SockOpts, []),
+ {keep_state_and_data, [{reply, From, OptsReply}]};
+handle_call({set_opts, Opts0}, From, StateName,
+ #state{static_env = #static_env{protocol_cb = Connection,
+ socket = Socket,
+ transport_cb = Transport,
+ trackers = Trackers},
+ connection_env =
+ #connection_env{user_application = {_Mon, Pid}},
+ socket_options = Opts1
+ } = State0) ->
+ {Reply, Opts} = set_socket_opts(Connection, Transport, Socket, Opts0, Opts1, []),
+ case {proplists:lookup(active, Opts0), Opts} of
+ {{_, N}, #socket_options{active=false}} when is_integer(N) ->
+ send_user(
+ Pid,
+ format_passive(
+ Connection:pids(State0), Transport, Socket, Trackers, Connection));
+ _ ->
+ ok
+ end,
+ State = State0#state{socket_options = Opts},
+ handle_active_option(Opts#socket_options.active, StateName, From, Reply, State);
+
+handle_call(renegotiate, From, StateName, _) when StateName =/= connection ->
+ {keep_state_and_data, [{reply, From, {error, already_renegotiating}}]};
+
+handle_call({prf, Secret, Label, Seed, WantedLength}, From, _,
+ #state{connection_states = ConnectionStates,
+ connection_env = #connection_env{negotiated_version = Version}}) ->
+ #{security_parameters := SecParams} =
+ ssl_record:current_connection_state(ConnectionStates, read),
+ #security_parameters{master_secret = MasterSecret,
+ client_random = ClientRandom,
+ server_random = ServerRandom,
+ prf_algorithm = PRFAlgorithm} = SecParams,
+ Reply = try
+ SecretToUse = case Secret of
+ _ when is_binary(Secret) -> Secret;
+ master_secret -> MasterSecret
+ end,
+ SeedToUse = lists:reverse(
+ lists:foldl(fun(X, Acc) when is_binary(X) -> [X|Acc];
+ (client_random, Acc) -> [ClientRandom|Acc];
+ (server_random, Acc) -> [ServerRandom|Acc]
+ end, [], Seed)),
+ ssl_handshake:prf(ssl:tls_version(Version), PRFAlgorithm, SecretToUse, Label, SeedToUse, WantedLength)
+ catch
+ exit:_ -> {error, badarg};
+ error:Reason -> {error, Reason}
+ end,
+ {keep_state_and_data, [{reply, From, Reply}]};
+handle_call(_,_,_,_) ->
+ {keep_state_and_data, [postpone]}.
+handle_info({ErrorTag, Socket, econnaborted}, StateName,
+ #state{static_env = #static_env{role = Role,
+ host = Host,
+ port = Port,
+ socket = Socket,
+ transport_cb = Transport,
+ error_tag = ErrorTag,
+ trackers = Trackers,
+ protocol_cb = Connection},
+ handshake_env = #handshake_env{renegotiation = Type},
+ connection_env = #connection_env{negotiated_version = Version},
+ session = Session,
+ start_or_recv_from = StartFrom
+ } = State) when StateName =/= connection ->
+
+ maybe_invalidate_session(Version, Type, Role, Host, Port, Session),
+ Pids = Connection:pids(State),
+ alert_user(Pids, Transport, Trackers,Socket,
+ StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role, StateName, Connection),
+ {stop, {shutdown, normal}, State};
+
+handle_info({ErrorTag, Socket, Reason}, StateName, #state{static_env = #static_env{
+ role = Role,
+ socket = Socket,
+ error_tag = ErrorTag},
+ ssl_options = #{log_level := Level}} = State) ->
+ ssl_logger:log(info, Level, #{description => "Socket error",
+ reason => [{error_tag, ErrorTag}, {description, Reason}]}, ?LOCATION),
+ Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, {transport_error, Reason}),
+ handle_normal_shutdown(Alert#alert{role = Role}, StateName, State),
+ {stop, {shutdown,normal}, State};
+
+handle_info({'DOWN', MonitorRef, _, _, Reason}, _,
+ #state{connection_env = #connection_env{user_application = {MonitorRef, _Pid}},
+ ssl_options = #{erl_dist := true}}) ->
+ {stop, {shutdown, Reason}};
+handle_info({'DOWN', MonitorRef, _, _, _}, _,
+ #state{connection_env = #connection_env{user_application = {MonitorRef, _Pid}}}) ->
+ {stop, {shutdown, normal}};
+handle_info({'EXIT', Pid, _Reason}, StateName,
+ #state{connection_env = #connection_env{user_application = {_MonitorRef, Pid}}} = State) ->
+ %% It seems the user application has linked to us
+ %% - ignore that and let the monitor handle this
+ {next_state, StateName, State};
+%%% So that terminate will be run when supervisor issues shutdown
+handle_info({'EXIT', _Sup, shutdown}, _StateName, State) ->
+ {stop, shutdown, State};
+handle_info({'EXIT', Socket, normal}, _StateName, #state{static_env = #static_env{socket = Socket}} = State) ->
+ %% Handle as transport close"
+ {stop,{shutdown, transport_closed}, State};
+handle_info({'EXIT', Socket, Reason}, _StateName, #state{static_env = #static_env{socket = Socket}} = State) ->
+ {stop,{shutdown, Reason}, State};
+handle_info(allow_renegotiate, StateName, #state{handshake_env = HsEnv} = State) -> %% PRE TLS-1.3
+ {next_state, StateName, State#state{handshake_env = HsEnv#handshake_env{allow_renegotiate = true}}};
+handle_info(Msg, StateName, #state{static_env = #static_env{socket = Socket, error_tag = ErrorTag},
+ ssl_options = #{log_level := Level}} = State) ->
+ ssl_logger:log(notice, Level, #{description => "Unexpected INFO message",
+ reason => [{message, Msg}, {socket, Socket},
+ {error_tag, ErrorTag}]}, ?LOCATION),
+ {next_state, StateName, State}.
+
+%%====================================================================
+%% Application Data
+%%====================================================================
+read_application_data(Data,
+ #state{user_data_buffer =
+ {Front0,BufferSize0,Rear0},
+ connection_env =
+ #connection_env{erl_dist_handle = DHandle}}
+ = State) ->
+ Front = Front0,
+ BufferSize = BufferSize0 + byte_size(Data),
+ Rear = [Data|Rear0],
+ case DHandle of
+ undefined ->
+ read_application_data(State, Front, BufferSize, Rear);
+ _ ->
+ try read_application_dist_data(DHandle, Front, BufferSize, Rear) of
+ Buffer ->
+ {no_record, State#state{user_data_buffer = Buffer}}
+ catch error:_ ->
+ {stop,disconnect,
+ State#state{user_data_buffer = {Front,BufferSize,Rear}}}
+ end
+ end.
+passive_receive(#state{user_data_buffer = {Front,BufferSize,Rear},
+ %% Assert! Erl distribution uses active sockets
+ connection_env = #connection_env{erl_dist_handle = undefined}}
+ = State0, StateName, Connection, StartTimerAction) ->
+ case BufferSize of
+ 0 ->
+ Connection:next_event(StateName, no_record, State0, StartTimerAction);
+ _ ->
+ case read_application_data(State0, Front, BufferSize, Rear) of
+ {stop, _, _} = ShutdownError ->
+ ShutdownError;
+ {Record, State} ->
+ case State#state.start_or_recv_from of
+ undefined ->
+ %% Cancel recv timeout as data has been delivered
+ Connection:next_event(StateName, Record, State,
+ [{{timeout, recv}, infinity, timeout}]);
+ _ ->
+ Connection:next_event(StateName, Record, State, StartTimerAction)
+ end
+ end
+ end.
+
+%%====================================================================
+%% Hibernation
+%%====================================================================
+
+hibernate_after(connection = StateName,
+ #state{ssl_options= #{hibernate_after := HibernateAfter}} = State,
+ Actions) ->
+ {next_state, StateName, State, [{timeout, HibernateAfter, hibernate} | Actions]};
+hibernate_after(StateName, State, Actions) ->
+ {next_state, StateName, State, Actions}.
+
+%%====================================================================
+%% Alert and close handling
+%%====================================================================
+send_alert(Alert, connection, #state{static_env = #static_env{protocol_cb = Connection}} = State) ->
+ Connection:send_alert_in_connection(Alert, State);
+send_alert(Alert, _, #state{static_env = #static_env{protocol_cb = Connection}} = State) ->
+ Connection:send_alert(Alert, State).
+
+handle_own_alert(Alert0, _, StateName,
+ #state{static_env = #static_env{role = Role,
+ protocol_cb = Connection},
+ ssl_options = #{log_level := LogLevel}} = State) ->
+ try %% Try to tell the other side
+ send_alert(Alert0, StateName, State)
+ catch _:_ -> %% Can crash if we are in a uninitialized state
+ ignore
+ end,
+ try %% Try to tell the local user
+ Alert = Alert0#alert{role = Role},
+ log_alert(LogLevel, Role, Connection:protocol_name(), StateName, Alert),
+ handle_normal_shutdown(Alert,StateName, State)
+ catch _:_ ->
+ ok
+ end,
+ {stop, {shutdown, own_alert}, State}.
+
+handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role,
+ socket = Socket,
+ transport_cb = Transport,
+ protocol_cb = Connection,
+ trackers = Trackers},
+ handshake_env = #handshake_env{renegotiation = {false, first}},
+ start_or_recv_from = StartFrom} = State) ->
+ Pids = Connection:pids(State),
+ alert_user(Pids, Transport, Trackers, Socket, StartFrom, Alert, Role, StateName, Connection);
+
+handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role,
+ socket = Socket,
+ transport_cb = Transport,
+ protocol_cb = Connection,
+ trackers = Trackers},
+ connection_env = #connection_env{user_application = {_Mon, Pid}},
+ socket_options = Opts,
+ start_or_recv_from = RecvFrom} = State) ->
+ Pids = Connection:pids(State),
+ alert_user(Pids, Transport, Trackers, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role, StateName, Connection).
+
+handle_alert(#alert{level = ?FATAL} = Alert0, StateName,
+ #state{static_env = #static_env{role = Role,
+ socket = Socket,
+ host = Host,
+ port = Port,
+ trackers = Trackers,
+ transport_cb = Transport,
+ protocol_cb = Connection},
+ connection_env = #connection_env{user_application = {_Mon, Pid}},
+ ssl_options = #{log_level := LogLevel},
+ start_or_recv_from = From,
+ session = Session,
+ socket_options = Opts} = State) ->
+ invalidate_session(Role, Host, Port, Session),
+ Alert = Alert0#alert{role = opposite_role(Role)},
+ log_alert(LogLevel, Role, Connection:protocol_name(),
+ StateName, Alert),
+ Pids = Connection:pids(State),
+ alert_user(Pids, Transport, Trackers, Socket, StateName, Opts, Pid, From, Alert, Role, StateName, Connection),
+ {stop, {shutdown, normal}, State};
+
+handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert,
+ downgrade= StateName, State) ->
+ {next_state, StateName, State, [{next_event, internal, Alert}]};
+handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert0,
+ StateName, #state{static_env = #static_env{role = Role}} = State) ->
+ Alert = Alert0#alert{role = opposite_role(Role)},
+ handle_normal_shutdown(Alert, StateName, State),
+ {stop,{shutdown, peer_close}, State};
+handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert0, StateName,
+ #state{static_env = #static_env{role = Role,
+ protocol_cb = Connection},
+ handshake_env = #handshake_env{renegotiation = {true, internal}},
+ ssl_options = #{log_level := LogLevel}} = State) ->
+ Alert = Alert0#alert{role = opposite_role(Role)},
+ log_alert(LogLevel, Role,
+ Connection:protocol_name(), StateName, Alert),
+ handle_normal_shutdown(Alert, StateName, State),
+ {stop,{shutdown, peer_close}, State};
+
+handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, connection = StateName,
+ #state{static_env = #static_env{role = Role,
+ protocol_cb = Connection},
+ handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv,
+ ssl_options = #{log_level := LogLevel}
+ } = State0) ->
+ log_alert(LogLevel, Role,
+ Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}),
+ gen_statem:reply(From, {error, renegotiation_rejected}),
+ State = Connection:reinit_handshake_data(State0),
+ Connection:next_event(connection, no_record, State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}});
+
+handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName,
+ #state{static_env = #static_env{role = Role,
+ protocol_cb = Connection},
+ handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv,
+ ssl_options = #{log_level := LogLevel}
+ } = State0) ->
+ log_alert(LogLevel, Role,
+ Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}),
+ gen_statem:reply(From, {error, renegotiation_rejected}),
+ %% Go back to connection!
+ State = Connection:reinit(State0#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}}),
+ Connection:next_event(connection, no_record, State);
+
+%% Gracefully log and ignore all other warning alerts
+handle_alert(#alert{level = ?WARNING} = Alert, StateName,
+ #state{static_env = #static_env{role = Role,
+ protocol_cb = Connection},
+ ssl_options = #{log_level := LogLevel}} = State) ->
+ log_alert(LogLevel, Role,
+ Connection:protocol_name(), StateName,
+ Alert#alert{role = opposite_role(Role)}),
+ Connection:next_event(StateName, no_record, State).
+handle_trusted_certs_db(#state{ssl_options =
+ #{cacertfile := <<>>, cacerts := []}}) ->
+ %% No trusted certs specified
+ ok;
+handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref,
+ cert_db = CertDb},
+ ssl_options = #{cacertfile := <<>>}}) when CertDb =/= undefined ->
+ %% Certs provided as DER directly can not be shared
+ %% with other connections and it is safe to delete them when the connection ends.
+ ssl_pkix_db:remove_trusted_certs(Ref, CertDb);
+handle_trusted_certs_db(#state{static_env = #static_env{file_ref_db = undefined}}) ->
+ %% Something went wrong early (typically cacertfile does not
+ %% exist) so there is nothing to handle
+ ok;
+handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref,
+ file_ref_db = RefDb},
+ ssl_options = #{cacertfile := File}}) ->
+ case ssl_pkix_db:ref_count(Ref, RefDb, -1) of
+ 0 ->
+ ssl_manager:clean_cert_db(Ref, File);
+ _ ->
+ ok
+ end.
+
+maybe_invalidate_session({3, 4},_, _, _, _, _) ->
+ ok;
+maybe_invalidate_session({3, N}, Type, Role, Host, Port, Session) when N < 4 ->
+ maybe_invalidate_session(Type, Role, Host, Port, Session).
+
+maybe_invalidate_session({false, first}, server = Role, Host, Port, Session) ->
+ invalidate_session(Role, Host, Port, Session);
+maybe_invalidate_session(_, _, _, _, _) ->
+ ok.
+
+terminate(_, _, #state{connection_env = #connection_env{terminated = true}}) ->
+ %% Happens when user closes the connection using ssl:close/1
+ %% we want to guarantee that Transport:close has been called
+ %% when ssl:close/1 returns unless it is a downgrade where
+ %% we want to guarantee that close alert is received before
+ %% returning. In both cases terminate has been run manually
+ %% before run by gen_statem which will end up here
+ ok;
+terminate({shutdown, transport_closed} = Reason,
+ _StateName, #state{static_env = #static_env{protocol_cb = Connection,
+ socket = Socket,
+ transport_cb = Transport}} = State) ->
+ handle_trusted_certs_db(State),
+ Connection:close(Reason, Socket, Transport, undefined, undefined);
+terminate({shutdown, own_alert}, _StateName, #state{
+ static_env = #static_env{protocol_cb = Connection,
+ socket = Socket,
+ transport_cb = Transport}} = State) ->
+ handle_trusted_certs_db(State),
+ case application:get_env(ssl, alert_timeout) of
+ {ok, Timeout} when is_integer(Timeout) ->
+ Connection:close({timeout, Timeout}, Socket, Transport, undefined, undefined);
+ _ ->
+ Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, undefined, undefined)
+ end;
+terminate({shutdown, downgrade = Reason}, downgrade, #state{static_env = #static_env{protocol_cb = Connection,
+ transport_cb = Transport,
+ socket = Socket}
+ } = State) ->
+ handle_trusted_certs_db(State),
+ Connection:close(Reason, Socket, Transport, undefined, undefined);
+terminate(Reason, connection, #state{static_env = #static_env{
+ protocol_cb = Connection,
+ transport_cb = Transport,
+ socket = Socket},
+ connection_states = ConnectionStates,
+ ssl_options = #{padding_check := Check}
+ } = State) ->
+ handle_trusted_certs_db(State),
+ Alert = terminate_alert(Reason),
+ %% Send the termination ALERT if possible
+ catch (ok = Connection:send_alert_in_connection(Alert, State)),
+ Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, ConnectionStates, Check);
+terminate(Reason, _StateName, #state{static_env = #static_env{transport_cb = Transport,
+ protocol_cb = Connection,
+ socket = Socket}
+ } = State) ->
+ handle_trusted_certs_db(State),
+ Connection:close(Reason, Socket, Transport, undefined, undefined).
+
+%%====================================================================
+%% Log handling
+%%====================================================================
+format_status(normal, [_, StateName, State]) ->
+ [{data, [{"State", {StateName, State}}]}];
+format_status(terminate, [_, StateName, State]) ->
+ SslOptions = (State#state.ssl_options),
+ NewOptions = SslOptions#{password => ?SECRET_PRINTOUT,
+ cert => ?SECRET_PRINTOUT,
+ cacerts => ?SECRET_PRINTOUT,
+ key => ?SECRET_PRINTOUT,
+ dh => ?SECRET_PRINTOUT,
+ psk_identity => ?SECRET_PRINTOUT,
+ srp_identity => ?SECRET_PRINTOUT},
+ [{data, [{"State", {StateName, State#state{connection_states = ?SECRET_PRINTOUT,
+ protocol_buffers = ?SECRET_PRINTOUT,
+ user_data_buffer = ?SECRET_PRINTOUT,
+ handshake_env = ?SECRET_PRINTOUT,
+ connection_env = ?SECRET_PRINTOUT,
+ session = ?SECRET_PRINTOUT,
+ ssl_options = NewOptions,
+ flight_buffer = ?SECRET_PRINTOUT}
+ }}]}].
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+tls_connection_fsm(#{versions := [{3,4}]}) ->
+ tls_connection_1_3;
+tls_connection_fsm(_) ->
+ tls_connection.
+
+dtls_connection_fsm(_) ->
+ dtls_connection.
+
+next_statem_state([Version], client) ->
+ case ssl:tls_version(Version) of
+ {3,4} ->
+ wait_sh;
+ _ ->
+ hello
+ end;
+next_statem_state([Version], server) ->
+ case ssl:tls_version(Version) of
+ {3,4} ->
+ start;
+ _ ->
+ hello
+ end;
+next_statem_state(_, _) ->
+ hello.
+
+call(FsmPid, Event) ->
+ try gen_statem:call(FsmPid, Event)
+ catch
+ exit:{noproc, _} ->
+ {error, closed};
+ exit:{normal, _} ->
+ {error, closed};
+ exit:{{shutdown, _},_} ->
+ {error, closed}
+ end.
+
+check_hostname(#state{ssl_options = SslOptions}, Hostname) ->
+ case is_sni_value(Hostname) of
+ true ->
+ case is_hostname_recognized(SslOptions, Hostname) of
+ true ->
+ valid;
+ false ->
+ %% We should send an alert but for interoperability reasons we
+ %% allow the connection to be established.
+ %% ?ALERT_REC(?FATAL, ?UNRECOGNIZED_NAME)
+ unrecognized_name
+ end;
+ false ->
+ ?ALERT_REC(?FATAL, ?UNRECOGNIZED_NAME,
+ {sni_included_trailing_dot, Hostname})
+ end.
+
+is_sni_value(Hostname) ->
+ case hd(lists:reverse(Hostname)) of
+ $. ->
+ false;
+ _ ->
+ true
+ end.
+
+is_hostname_recognized(#{sni_fun := undefined,
+ sni_hosts := SNIHosts}, Hostname) ->
+ proplists:is_defined(Hostname, SNIHosts);
+is_hostname_recognized(_, _) ->
+ true.
+
+handle_sni_hostname(Hostname,
+ #state{static_env = #static_env{role = Role} = InitStatEnv0,
+ handshake_env = HsEnv,
+ connection_env = CEnv} = State0) ->
+ NewOptions = update_ssl_options_from_sni(State0#state.ssl_options, Hostname),
+ case NewOptions of
+ undefined ->
+ State0;
+ _ ->
+ {ok, #{cert_db_ref := Ref,
+ cert_db_handle := CertDbHandle,
+ fileref_db_handle := FileRefHandle,
+ session_cache := CacheHandle,
+ crl_db_info := CRLDbHandle,
+ private_key := Key,
+ dh_params := DHParams,
+ own_certificates := OwnCerts}} =
+ ssl_config:init(NewOptions, Role),
+ State0#state{
+ session = State0#state.session#session{own_certificates = OwnCerts},
+ static_env = InitStatEnv0#static_env{
+ file_ref_db = FileRefHandle,
+ cert_db_ref = Ref,
+ cert_db = CertDbHandle,
+ crl_db = CRLDbHandle,
+ session_cache = CacheHandle
+ },
+ connection_env = CEnv#connection_env{private_key = Key},
+ ssl_options = NewOptions,
+ handshake_env = HsEnv#handshake_env{sni_hostname = Hostname,
+ diffie_hellman_params = DHParams}
+ }
+ end.
+
+update_ssl_options_from_sni(#{sni_fun := SNIFun,
+ sni_hosts := SNIHosts} = OrigSSLOptions, SNIHostname) ->
+ SSLOption =
+ case SNIFun of
+ undefined ->
+ proplists:get_value(SNIHostname,
+ SNIHosts);
+ SNIFun ->
+ SNIFun(SNIHostname)
+ end,
+ case SSLOption of
+ undefined ->
+ undefined;
+ _ ->
+ ssl:handle_options(SSLOption, server, OrigSSLOptions)
+ end.
+
+set_sni_guided_cert_selection(#state{handshake_env = HsEnv0} = State, Bool) ->
+ HsEnv = HsEnv0#handshake_env{sni_guided_cert_selection = Bool},
+ State#state{handshake_env = HsEnv}.
+
+ack_connection(#state{handshake_env = #handshake_env{renegotiation = {true, Initiater}} = HsEnv} = State) when Initiater == peer;
+ Initiater == internal ->
+ State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}};
+ack_connection(#state{handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv} = State) ->
+ gen_statem:reply(From, ok),
+ State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}};
+ack_connection(#state{handshake_env = #handshake_env{renegotiation = {false, first}} = HsEnv,
+ start_or_recv_from = StartFrom} = State) when StartFrom =/= undefined ->
+ gen_statem:reply(StartFrom, connected),
+ State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined},
+ start_or_recv_from = undefined};
+ack_connection(State) ->
+ State.
+
+no_records(Extensions) ->
+ maps:map(fun(_, Value) ->
+ ssl_handshake:extension_value(Value)
+ end, Extensions).
+
+handle_active_option(false, connection = StateName, To, Reply, State) ->
+ hibernate_after(StateName, State, [{reply, To, Reply}]);
+
+handle_active_option(_, connection = StateName, To, Reply, #state{static_env = #static_env{role = Role},
+ connection_env = #connection_env{terminated = true},
+ user_data_buffer = {_,0,_}} = State) ->
+ Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, all_data_delivered),
+ handle_normal_shutdown(Alert#alert{role = Role}, StateName, State),
+ {stop_and_reply,{shutdown, peer_close}, [{reply, To, Reply}]};
+handle_active_option(_, connection = StateName0, To, Reply, #state{static_env = #static_env{protocol_cb = Connection},
+ user_data_buffer = {_,0,_}} = State0) ->
+ case Connection:next_event(StateName0, no_record, State0) of
+ {next_state, StateName, State} ->
+ hibernate_after(StateName, State, [{reply, To, Reply}]);
+ {next_state, StateName, State, Actions} ->
+ hibernate_after(StateName, State, [{reply, To, Reply} | Actions]);
+ {stop, _, _} = Stop ->
+ Stop
+ end;
+handle_active_option(_, StateName, To, Reply, #state{user_data_buffer = {_,0,_}} = State) ->
+ %% Active once already set
+ {next_state, StateName, State, [{reply, To, Reply}]};
+
+%% user_data_buffer nonempty
+handle_active_option(_, StateName0, To, Reply,
+ #state{static_env = #static_env{protocol_cb = Connection}} = State0) ->
+ case read_application_data(<<>>, State0) of
+ {stop, _, _} = Stop ->
+ Stop;
+ {Record, State1} ->
+ %% Note: Renogotiation may cause StateName0 =/= StateName
+ case Connection:next_event(StateName0, Record, State1) of
+ {next_state, StateName, State} ->
+ hibernate_after(StateName, State, [{reply, To, Reply}]);
+ {next_state, StateName, State, Actions} ->
+ hibernate_after(StateName, State, [{reply, To, Reply} | Actions]);
+ {stop, _, _} = Stop ->
+ Stop
+ end
+ end.
+
+read_application_data(#state{
+ socket_options = SocketOpts,
+ bytes_to_read = BytesToRead,
+ start_or_recv_from = RecvFrom} = State, Front, BufferSize, Rear) ->
+ read_application_data(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead).
+
+%% Pick binary from queue front, if empty wait for more data
+read_application_data(State, [Bin|Front], BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead) ->
+ read_application_data_bin(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead, Bin);
+read_application_data(State, [] = Front, BufferSize, [] = Rear, SocketOpts, RecvFrom, BytesToRead) ->
+ 0 = BufferSize, % Assert
+ {no_record, State#state{socket_options = SocketOpts,
+ bytes_to_read = BytesToRead,
+ start_or_recv_from = RecvFrom,
+ user_data_buffer = {Front,BufferSize,Rear}}};
+read_application_data(State, [], BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead) ->
+ [Bin|Front] = lists:reverse(Rear),
+ read_application_data_bin(State, Front, BufferSize, [], SocketOpts, RecvFrom, BytesToRead, Bin).
+
+read_application_data_bin(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead, <<>>) ->
+ %% Done with this binary - get next
+ read_application_data(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead);
+read_application_data_bin(State, Front0, BufferSize0, Rear0, SocketOpts0, RecvFrom, BytesToRead, Bin0) ->
+ %% Decode one packet from a binary
+ case get_data(SocketOpts0, BytesToRead, Bin0) of
+ {ok, Data, Bin} -> % Send data
+ BufferSize = BufferSize0 - (byte_size(Bin0) - byte_size(Bin)),
+ read_application_data_deliver(
+ State, [Bin|Front0], BufferSize, Rear0, SocketOpts0, RecvFrom, Data);
+ {more, undefined} ->
+ %% We need more data, do not know how much
+ if
+ byte_size(Bin0) < BufferSize0 ->
+ %% We have more data in the buffer besides the first binary - concatenate all and retry
+ Bin = iolist_to_binary([Bin0,Front0|lists:reverse(Rear0)]),
+ read_application_data_bin(
+ State, [], BufferSize0, [], SocketOpts0, RecvFrom, BytesToRead, Bin);
+ true ->
+ %% All data is in the first binary, no use to retry - wait for more
+ {no_record, State#state{socket_options = SocketOpts0,
+ bytes_to_read = BytesToRead,
+ start_or_recv_from = RecvFrom,
+ user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}}
+ end;
+ {more, Size} when Size =< BufferSize0 ->
+ %% We have a packet in the buffer - collect it in a binary and decode
+ {Data,Front,Rear} = iovec_from_front(Size - byte_size(Bin0), Front0, Rear0, [Bin0]),
+ Bin = iolist_to_binary(Data),
+ read_application_data_bin(
+ State, Front, BufferSize0, Rear, SocketOpts0, RecvFrom, BytesToRead, Bin);
+ {more, _Size} ->
+ %% We do not have a packet in the buffer - wait for more
+ {no_record, State#state{socket_options = SocketOpts0,
+ bytes_to_read = BytesToRead,
+ start_or_recv_from = RecvFrom,
+ user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}};
+ passive ->
+ {no_record, State#state{socket_options = SocketOpts0,
+ bytes_to_read = BytesToRead,
+ start_or_recv_from = RecvFrom,
+ user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}};
+ {error,_Reason} ->
+ %% Invalid packet in packet mode
+ #state{
+ static_env =
+ #static_env{
+ socket = Socket,
+ protocol_cb = Connection,
+ transport_cb = Transport,
+ trackers = Trackers},
+ connection_env =
+ #connection_env{user_application = {_Mon, Pid}}} = State,
+ Buffer = iolist_to_binary([Bin0,Front0|lists:reverse(Rear0)]),
+ deliver_packet_error(
+ Connection:pids(State), Transport, Socket, SocketOpts0,
+ Buffer, Pid, RecvFrom, Trackers, Connection),
+ {stop, {shutdown, normal}, State#state{socket_options = SocketOpts0,
+ bytes_to_read = BytesToRead,
+ start_or_recv_from = RecvFrom,
+ user_data_buffer = {[Buffer],BufferSize0,[]}}}
+ end.
+
+read_application_data_deliver(State, Front, BufferSize, Rear, SocketOpts0, RecvFrom, Data) ->
+ #state{
+ static_env =
+ #static_env{
+ socket = Socket,
+ protocol_cb = Connection,
+ transport_cb = Transport,
+ trackers = Trackers},
+ connection_env =
+ #connection_env{user_application = {_Mon, Pid}}} = State,
+ SocketOpts =
+ deliver_app_data(
+ Connection:pids(State), Transport, Socket, SocketOpts0, Data, Pid, RecvFrom, Trackers, Connection),
+ if
+ SocketOpts#socket_options.active =:= false ->
+ %% Passive mode, wait for active once or recv
+ {no_record,
+ State#state{
+ user_data_buffer = {Front,BufferSize,Rear},
+ start_or_recv_from = undefined,
+ bytes_to_read = undefined,
+ socket_options = SocketOpts
+ }};
+ true -> %% Try to deliver more data
+ read_application_data(State, Front, BufferSize, Rear, SocketOpts, undefined, undefined)
+ end.
+
+
+read_application_dist_data(DHandle, [Bin|Front], BufferSize, Rear) ->
+ read_application_dist_data(DHandle, Front, BufferSize, Rear, Bin);
+read_application_dist_data(_DHandle, [] = Front, BufferSize, [] = Rear) ->
+ BufferSize = 0,
+ {Front,BufferSize,Rear};
+read_application_dist_data(DHandle, [], BufferSize, Rear) ->
+ [Bin|Front] = lists:reverse(Rear),
+ read_application_dist_data(DHandle, Front, BufferSize, [], Bin).
+%%
+read_application_dist_data(DHandle, Front0, BufferSize, Rear0, Bin0) ->
+ case Bin0 of
+ %%
+ %% START Optimization
+ %% It is cheaper to match out several packets in one match operation than to loop for each
+ <<SizeA:32, DataA:SizeA/binary,
+ SizeB:32, DataB:SizeB/binary,
+ SizeC:32, DataC:SizeC/binary,
+ SizeD:32, DataD:SizeD/binary, Rest/binary>>
+ when 0 < SizeA, 0 < SizeB, 0 < SizeC, 0 < SizeD ->
+ %% We have 4 complete packets in the first binary
+ erlang:dist_ctrl_put_data(DHandle, DataA),
+ erlang:dist_ctrl_put_data(DHandle, DataB),
+ erlang:dist_ctrl_put_data(DHandle, DataC),
+ erlang:dist_ctrl_put_data(DHandle, DataD),
+ read_application_dist_data(
+ DHandle, Front0, BufferSize - (4*4+SizeA+SizeB+SizeC+SizeD), Rear0, Rest);
+ <<SizeA:32, DataA:SizeA/binary,
+ SizeB:32, DataB:SizeB/binary,
+ SizeC:32, DataC:SizeC/binary, Rest/binary>>
+ when 0 < SizeA, 0 < SizeB, 0 < SizeC ->
+ %% We have 3 complete packets in the first binary
+ erlang:dist_ctrl_put_data(DHandle, DataA),
+ erlang:dist_ctrl_put_data(DHandle, DataB),
+ erlang:dist_ctrl_put_data(DHandle, DataC),
+ read_application_dist_data(
+ DHandle, Front0, BufferSize - (3*4+SizeA+SizeB+SizeC), Rear0, Rest);
+ <<SizeA:32, DataA:SizeA/binary,
+ SizeB:32, DataB:SizeB/binary, Rest/binary>>
+ when 0 < SizeA, 0 < SizeB ->
+ %% We have 2 complete packets in the first binary
+ erlang:dist_ctrl_put_data(DHandle, DataA),
+ erlang:dist_ctrl_put_data(DHandle, DataB),
+ read_application_dist_data(
+ DHandle, Front0, BufferSize - (2*4+SizeA+SizeB), Rear0, Rest);
+ %% END Optimization
+ %%
+ %% Basic one packet code path
+ <<Size:32, Data:Size/binary, Rest/binary>> ->
+ %% We have a complete packet in the first binary
+ 0 < Size andalso erlang:dist_ctrl_put_data(DHandle, Data),
+ read_application_dist_data(DHandle, Front0, BufferSize - (4+Size), Rear0, Rest);
+ <<Size:32, FirstData/binary>> when 4+Size =< BufferSize ->
+ %% We have a complete packet in the buffer
+ %% - fetch the missing content from the buffer front
+ {Data,Front,Rear} = iovec_from_front(Size - byte_size(FirstData), Front0, Rear0, [FirstData]),
+ 0 < Size andalso erlang:dist_ctrl_put_data(DHandle, Data),
+ read_application_dist_data(DHandle, Front, BufferSize - (4+Size), Rear);
+ <<Bin/binary>> ->
+ %% In OTP-21 the match context reuse optimization fails if we use Bin0 in recursion, so here we
+ %% match out the whole binary which will trick the optimization into keeping the match context
+ %% for the first binary contains complete packet code above
+ case Bin of
+ <<_Size:32, _InsufficientData/binary>> ->
+ %% We have a length field in the first binary but there is not enough data
+ %% in the buffer to form a complete packet - await more data
+ {[Bin|Front0],BufferSize,Rear0};
+ <<IncompleteLengthField/binary>> when 4 < BufferSize ->
+ %% We do not have a length field in the first binary but the buffer
+ %% contains enough data to maybe form a packet
+ %% - fetch a tiny binary from the buffer front to complete the length field
+ {LengthField,Front,Rear} =
+ case IncompleteLengthField of
+ <<>> ->
+ iovec_from_front(4, Front0, Rear0, []);
+ _ ->
+ iovec_from_front(
+ 4 - byte_size(IncompleteLengthField), Front0, Rear0, [IncompleteLengthField])
+ end,
+ LengthBin = iolist_to_binary(LengthField),
+ read_application_dist_data(DHandle, Front, BufferSize, Rear, LengthBin);
+ <<IncompleteLengthField/binary>> ->
+ %% We do not have enough data in the buffer to even form a length field - await more data
+ case IncompleteLengthField of
+ <<>> ->
+ {Front0,BufferSize,Rear0};
+ _ ->
+ {[IncompleteLengthField|Front0],BufferSize,Rear0}
+ end
+ end
+ end.
+
+iovec_from_front(0, Front, Rear, Acc) ->
+ {lists:reverse(Acc),Front,Rear};
+iovec_from_front(Size, [], Rear, Acc) ->
+ case Rear of
+ %% Avoid lists:reverse/1 for simple cases.
+ %% Case clause for [] to avoid infinite loop.
+ [_] ->
+ iovec_from_front(Size, Rear, [], Acc);
+ [Bin2,Bin1] ->
+ iovec_from_front(Size, [Bin1,Bin2], [], Acc);
+ [Bin3,Bin2,Bin1] ->
+ iovec_from_front(Size, [Bin1,Bin2,Bin3], [], Acc);
+ [_,_,_|_] = Rear ->
+ iovec_from_front(Size, lists:reverse(Rear), [], Acc)
+ end;
+iovec_from_front(Size, [Bin|Front], Rear, []) ->
+ case Bin of
+ <<Last:Size/binary>> -> % Just enough
+ {[Last],Front,Rear};
+ <<Last:Size/binary, Rest/binary>> -> % More than enough, split here
+ {[Last],[Rest|Front],Rear};
+ <<>> -> % Not enough, skip empty binaries
+ iovec_from_front(Size, Front, Rear, []);
+ <<_/binary>> -> % Not enough
+ BinSize = byte_size(Bin),
+ iovec_from_front(Size - BinSize, Front, Rear, [Bin])
+ end;
+iovec_from_front(Size, [Bin|Front], Rear, Acc) ->
+ case Bin of
+ <<Last:Size/binary>> -> % Just enough
+ {lists:reverse(Acc, [Last]),Front,Rear};
+ <<Last:Size/binary, Rest/binary>> -> % More than enough, split here
+ {lists:reverse(Acc, [Last]),[Rest|Front],Rear};
+ <<>> -> % Not enough, skip empty binaries
+ iovec_from_front(Size, Front, Rear, Acc);
+ <<_/binary>> -> % Not enough
+ BinSize = byte_size(Bin),
+ iovec_from_front(Size - BinSize, Front, Rear, [Bin|Acc])
+ end.
+%% Picks ClientData
+get_data(#socket_options{active=false}, undefined, _Bin) ->
+ %% Recv timed out save buffer data until next recv
+ passive;
+get_data(#socket_options{active=Active, packet=Raw}, BytesToRead, Bin)
+ when Raw =:= raw; Raw =:= 0 -> %% Raw Mode
+ case Bin of
+ <<_/binary>> when Active =/= false orelse BytesToRead =:= 0 ->
+ %% Active true or once, or passive mode recv(0)
+ {ok, Bin, <<>>};
+ <<Data:BytesToRead/binary, Rest/binary>> ->
+ %% Passive Mode, recv(Bytes)
+ {ok, Data, Rest};
+ <<_/binary>> ->
+ %% Passive Mode not enough data
+ {more, BytesToRead}
+ end;
+get_data(#socket_options{packet=Type, packet_size=Size}, _, Bin) ->
+ PacketOpts = [{packet_size, Size}],
+ decode_packet(Type, Bin, PacketOpts).
+
+decode_packet({http, headers}, Buffer, PacketOpts) ->
+ decode_packet(httph, Buffer, PacketOpts);
+decode_packet({http_bin, headers}, Buffer, PacketOpts) ->
+ decode_packet(httph_bin, Buffer, PacketOpts);
+decode_packet(Type, Buffer, PacketOpts) ->
+ erlang:decode_packet(Type, Buffer, PacketOpts).
+
+%% Just like with gen_tcp sockets, an ssl socket that has been configured with
+%% {packet, http} (or {packet, http_bin}) will automatically switch to expect
+%% HTTP headers after it sees a HTTP Request or HTTP Response line. We
+%% represent the current state as follows:
+%% #socket_options.packet =:= http: Expect a HTTP Request/Response line
+%% #socket_options.packet =:= {http, headers}: Expect HTTP Headers
+%% Note that if the user has explicitly configured the socket to expect
+%% HTTP headers using the {packet, httph} option, we don't do any automatic
+%% switching of states.
+deliver_app_data(CPids, Transport, Socket,
+ #socket_options{active=Active, packet=Type} = SOpts,
+ Data, Pid, From, Trackers, Connection) ->
+ send_or_reply(Active, Pid, From,
+ format_reply(CPids, Transport, Socket,
+ SOpts, Data, Trackers, Connection)),
+ SO =
+ case Data of
+ {P, _, _, _}
+ when ((P =:= http_request) or (P =:= http_response)),
+ ((Type =:= http) or (Type =:= http_bin)) ->
+ SOpts#socket_options{packet={Type, headers}};
+ http_eoh when tuple_size(Type) =:= 2 ->
+ %% End of headers - expect another Request/Response line
+ {Type1, headers} = Type,
+ SOpts#socket_options{packet=Type1};
+ _ ->
+ SOpts
+ end,
+ case Active of
+ once ->
+ SO#socket_options{active=false};
+ 1 ->
+ send_user(Pid,
+ format_passive(CPids, Transport,
+ Socket, Trackers, Connection)),
+ SO#socket_options{active=false};
+ N when is_integer(N) ->
+ SO#socket_options{active=N - 1};
+ _ ->
+ SO
+ end.
+
+format_reply(_, _, _,#socket_options{active = false, mode = Mode, packet = Packet,
+ header = Header}, Data, _, _) ->
+ {ok, do_format_reply(Mode, Packet, Header, Data)};
+format_reply(CPids, Transport, Socket, #socket_options{active = _, mode = Mode, packet = Packet,
+ header = Header}, Data, Trackers, Connection) ->
+ {ssl, Connection:socket(CPids, Transport, Socket, Trackers),
+ do_format_reply(Mode, Packet, Header, Data)}.
+
+deliver_packet_error(CPids, Transport, Socket,
+ SO= #socket_options{active = Active}, Data, Pid, From, Trackers, Connection) ->
+ send_or_reply(Active, Pid, From, format_packet_error(CPids,
+ Transport, Socket, SO, Data, Trackers, Connection)).
+
+format_packet_error(_, _, _,#socket_options{active = false, mode = Mode}, Data, _, _) ->
+ {error, {invalid_packet, do_format_reply(Mode, raw, 0, Data)}};
+format_packet_error(CPids, Transport, Socket, #socket_options{active = _, mode = Mode},
+ Data, Trackers, Connection) ->
+ {ssl_error, Connection:socket(CPids, Transport, Socket, Trackers),
+ {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}.
+
+do_format_reply(binary, _, N, Data) when N > 0 -> % Header mode
+ header(N, Data);
+do_format_reply(binary, _, _, Data) ->
+ Data;
+do_format_reply(list, Packet, _, Data)
+ when Packet == http; Packet == {http, headers};
+ Packet == http_bin; Packet == {http_bin, headers};
+ Packet == httph; Packet == httph_bin ->
+ Data;
+do_format_reply(list, _,_, Data) ->
+ binary_to_list(Data).
+
+format_passive(CPids, Transport, Socket, Trackers, Connection) ->
+ {ssl_passive, Connection:socket(CPids, Transport, Socket, Trackers)}.
+
+header(0, <<>>) ->
+ <<>>;
+header(_, <<>>) ->
+ [];
+header(0, Binary) ->
+ Binary;
+header(N, Binary) ->
+ <<?BYTE(ByteN), NewBinary/binary>> = Binary,
+ [ByteN | header(N-1, NewBinary)].
+
+send_or_reply(false, _Pid, From, Data) when From =/= undefined ->
+ gen_statem:reply(From, Data);
+send_or_reply(false, Pid, undefined, _) when is_pid(Pid) ->
+ ok;
+send_or_reply(_, no_pid, _, _) ->
+ ok;
+send_or_reply(_, Pid, _, Data) ->
+ send_user(Pid, Data).
+
+send_user(Pid, Msg) ->
+ Pid ! Msg,
+ ok.
+
+alert_user(Pids, Transport, Trackers, Socket, connection, Opts, Pid, From, Alert, Role, StateName, Connection) ->
+ alert_user(Pids, Transport, Trackers, Socket, Opts#socket_options.active, Pid, From, Alert, Role, StateName, Connection);
+alert_user(Pids, Transport, Trackers, Socket,_, _, _, From, Alert, Role, StateName, Connection) ->
+ alert_user(Pids, Transport, Trackers, Socket, From, Alert, Role, StateName, Connection).
+
+alert_user(Pids, Transport, Trackers, Socket, From, Alert, Role, StateName, Connection) ->
+ alert_user(Pids, Transport, Trackers, Socket, false, no_pid, From, Alert, Role, StateName, Connection).
+
+alert_user(_, _, _, _, false = Active, Pid, From, Alert, Role, StateName, Connection) when From =/= undefined ->
+ %% If there is an outstanding ssl_accept | recv
+ %% From will be defined and send_or_reply will
+ %% send the appropriate error message.
+ ReasonCode = ssl_alert:reason_code(Alert, Role, Connection:protocol_name(), StateName),
+ send_or_reply(Active, Pid, From, {error, ReasonCode});
+alert_user(Pids, Transport, Trackers, Socket, Active, Pid, From, Alert, Role, StateName, Connection) ->
+ case ssl_alert:reason_code(Alert, Role, Connection:protocol_name(), StateName) of
+ closed ->
+ send_or_reply(Active, Pid, From,
+ {ssl_closed, Connection:socket(Pids, Transport, Socket, Trackers)});
+ ReasonCode ->
+ send_or_reply(Active, Pid, From,
+ {ssl_error, Connection:socket(Pids, Transport, Socket, Trackers), ReasonCode})
+ end.
+
+log_alert(Level, Role, ProtocolName, StateName, #alert{role = Role} = Alert) ->
+ ssl_logger:log(notice, Level, #{protocol => ProtocolName,
+ role => Role,
+ statename => StateName,
+ alert => Alert,
+ alerter => own}, Alert#alert.where);
+log_alert(Level, Role, ProtocolName, StateName, Alert) ->
+ ssl_logger:log(notice, Level, #{protocol => ProtocolName,
+ role => Role,
+ statename => StateName,
+ alert => Alert,
+ alerter => peer}, Alert#alert.where).
+terminate_alert(normal) ->
+ ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY);
+terminate_alert({Reason, _}) when Reason == close;
+ Reason == shutdown ->
+ ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY);
+terminate_alert(_) ->
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR).
+
+invalidate_session(client, Host, Port, Session) ->
+ ssl_manager:invalidate_session(Host, Port, Session);
+invalidate_session(server, _, _, _) ->
+ ok.
+
+opposite_role(client) ->
+ server;
+opposite_role(server) ->
+ client.
+
+connection_info(#state{handshake_env = #handshake_env{sni_hostname = SNIHostname,
+ resumption = Resumption},
+ session = #session{session_id = SessionId,
+ cipher_suite = CipherSuite,
+ srp_username = SrpUsername,
+ ecc = ECCCurve} = Session,
+ connection_states = #{current_write := CurrentWrite},
+ connection_env = #connection_env{negotiated_version = {_,_} = Version},
+ ssl_options = #{protocol := Protocol} = Opts}) ->
+ RecordCB = record_cb(Protocol),
+ CipherSuiteDef = #{key_exchange := KexAlg} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
+ IsNamedCurveSuite = lists:member(KexAlg,
+ [ecdh_ecdsa, ecdhe_ecdsa, ecdh_rsa, ecdhe_rsa, ecdh_anon]),
+ CurveInfo = case ECCCurve of
+ {namedCurve, Curve} when IsNamedCurveSuite ->
+ [{ecc, {named_curve, pubkey_cert_records:namedCurves(Curve)}}];
+ _ ->
+ []
+ end,
+ MFLInfo = case maps:get(max_fragment_length, CurrentWrite, undefined) of
+ MaxFragmentLength when is_integer(MaxFragmentLength) ->
+ [{max_fragment_length, MaxFragmentLength}];
+ _ ->
+ []
+ end,
+ [{protocol, RecordCB:protocol_version(Version)},
+ {session_id, SessionId},
+ {session_data, term_to_binary(Session)},
+ {session_resumption, Resumption},
+ {selected_cipher_suite, CipherSuiteDef},
+ {sni_hostname, SNIHostname},
+ {srp_username, SrpUsername} | CurveInfo] ++ MFLInfo ++ ssl_options_list(Opts).
+
+security_info(#state{connection_states = ConnectionStates,
+ static_env = #static_env{role = Role},
+ ssl_options = #{keep_secrets := KeepSecrets}}) ->
+ ReadState = ssl_record:current_connection_state(ConnectionStates, read),
+ #{security_parameters :=
+ #security_parameters{client_random = ClientRand,
+ server_random = ServerRand,
+ master_secret = MasterSecret,
+ application_traffic_secret = AppTrafSecretRead}} = ReadState,
+ BaseSecurityInfo = [{client_random, ClientRand}, {server_random, ServerRand}, {master_secret, MasterSecret}],
+ if KeepSecrets =/= true ->
+ BaseSecurityInfo;
+ true ->
+ #{security_parameters :=
+ #security_parameters{application_traffic_secret = AppTrafSecretWrite}} =
+ ssl_record:current_connection_state(ConnectionStates, write),
+ BaseSecurityInfo ++
+ if Role == server ->
+ [{server_traffic_secret_0, AppTrafSecretWrite}, {client_traffic_secret_0, AppTrafSecretRead}];
+ true ->
+ [{client_traffic_secret_0, AppTrafSecretWrite}, {server_traffic_secret_0, AppTrafSecretRead}]
+ end ++
+ case ReadState of
+ #{client_handshake_traffic_secret := ClientHSTrafficSecret,
+ server_handshake_traffic_secret := ServerHSTrafficSecret} ->
+ [{client_handshake_traffic_secret, ClientHSTrafficSecret},
+ {server_handshake_traffic_secret, ServerHSTrafficSecret}];
+ _ ->
+ []
+ end
+ end.
+
+record_cb(tls) ->
+ tls_record;
+record_cb(dtls) ->
+ dtls_record.
+
+get_socket_opts(_, _,_,[], _, Acc) ->
+ {ok, Acc};
+get_socket_opts(Connection, Transport, Socket, [mode | Tags], SockOpts, Acc) ->
+ get_socket_opts(Connection, Transport, Socket, Tags, SockOpts,
+ [{mode, SockOpts#socket_options.mode} | Acc]);
+get_socket_opts(Connection, Transport, Socket, [packet | Tags], SockOpts, Acc) ->
+ case SockOpts#socket_options.packet of
+ {Type, headers} ->
+ get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]);
+ Type ->
+ get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc])
+ end;
+get_socket_opts(Connection, Transport, Socket, [header | Tags], SockOpts, Acc) ->
+ get_socket_opts(Connection, Transport, Socket, Tags, SockOpts,
+ [{header, SockOpts#socket_options.header} | Acc]);
+get_socket_opts(Connection, Transport, Socket, [active | Tags], SockOpts, Acc) ->
+ get_socket_opts(Connection, Transport, Socket, Tags, SockOpts,
+ [{active, SockOpts#socket_options.active} | Acc]);
+get_socket_opts(Connection, Transport, Socket, [Tag | Tags], SockOpts, Acc) ->
+ case Connection:getopts(Transport, Socket, [Tag]) of
+ {ok, [Opt]} ->
+ get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [Opt | Acc]);
+ {error, Reason} ->
+ {error, {options, {socket_options, Tag, Reason}}}
+ end;
+get_socket_opts(_,_, _,Opts, _,_) ->
+ {error, {options, {socket_options, Opts, function_clause}}}.
+
+set_socket_opts(_,_,_, [], SockOpts, []) ->
+ {ok, SockOpts};
+set_socket_opts(ConnectionCb, Transport, Socket, [], SockOpts, Other) ->
+ %% Set non emulated options
+ try ConnectionCb:setopts(Transport, Socket, Other) of
+ ok ->
+ {ok, SockOpts};
+ {error, InetError} ->
+ {{error, {options, {socket_options, Other, InetError}}}, SockOpts}
+ catch
+ _:Error ->
+ %% So that inet behavior does not crash our process
+ {{error, {options, {socket_options, Other, Error}}}, SockOpts}
+ end;
+
+set_socket_opts(ConnectionCb, Transport,Socket, [{mode, Mode}| Opts], SockOpts, Other)
+ when Mode == list; Mode == binary ->
+ set_socket_opts(ConnectionCb, Transport, Socket, Opts,
+ SockOpts#socket_options{mode = Mode}, Other);
+set_socket_opts(_, _, _, [{mode, _} = Opt| _], SockOpts, _) ->
+ {{error, {options, {socket_options, Opt}}}, SockOpts};
+set_socket_opts(ConnectionCb, Transport,Socket, [{packet, Packet}| Opts], SockOpts, Other)
+ when Packet == raw;
+ Packet == 0;
+ Packet == 1;
+ Packet == 2;
+ Packet == 4;
+ Packet == asn1;
+ Packet == cdr;
+ Packet == sunrm;
+ Packet == fcgi;
+ Packet == tpkt;
+ Packet == line;
+ Packet == http;
+ Packet == httph;
+ Packet == http_bin;
+ Packet == httph_bin ->
+ set_socket_opts(ConnectionCb, Transport, Socket, Opts,
+ SockOpts#socket_options{packet = Packet}, Other);
+set_socket_opts(_, _, _, [{packet, _} = Opt| _], SockOpts, _) ->
+ {{error, {options, {socket_options, Opt}}}, SockOpts};
+set_socket_opts(ConnectionCb, Transport, Socket, [{header, Header}| Opts], SockOpts, Other)
+ when is_integer(Header) ->
+ set_socket_opts(ConnectionCb, Transport, Socket, Opts,
+ SockOpts#socket_options{header = Header}, Other);
+set_socket_opts(_, _, _, [{header, _} = Opt| _], SockOpts, _) ->
+ {{error,{options, {socket_options, Opt}}}, SockOpts};
+set_socket_opts(ConnectionCb, Transport, Socket, [{active, Active}| Opts], SockOpts, Other)
+ when Active == once;
+ Active == true;
+ Active == false ->
+ set_socket_opts(ConnectionCb, Transport, Socket, Opts,
+ SockOpts#socket_options{active = Active}, Other);
+set_socket_opts(ConnectionCb, Transport, Socket, [{active, Active1} = Opt| Opts],
+ SockOpts=#socket_options{active = Active0}, Other)
+ when Active1 >= -32768, Active1 =< 32767 ->
+ Active = if
+ is_integer(Active0), Active0 + Active1 < -32768 ->
+ error;
+ is_integer(Active0), Active0 + Active1 =< 0 ->
+ false;
+ is_integer(Active0), Active0 + Active1 > 32767 ->
+ error;
+ Active1 =< 0 ->
+ false;
+ is_integer(Active0) ->
+ Active0 + Active1;
+ true ->
+ Active1
+ end,
+ case Active of
+ error ->
+ {{error, {options, {socket_options, Opt}} }, SockOpts};
+ _ ->
+ set_socket_opts(ConnectionCb, Transport, Socket, Opts,
+ SockOpts#socket_options{active = Active}, Other)
+ end;
+set_socket_opts(_,_, _, [{active, _} = Opt| _], SockOpts, _) ->
+ {{error, {options, {socket_options, Opt}} }, SockOpts};
+set_socket_opts(ConnectionCb, Transport, Socket, [Opt | Opts], SockOpts, Other) ->
+ set_socket_opts(ConnectionCb, Transport, Socket, Opts, SockOpts, [Opt | Other]).
+ssl_options_list(SslOptions) ->
+ L = maps:to_list(SslOptions),
+ ssl_options_list(L, []).
+
+new_emulated([], EmOpts) ->
+ EmOpts;
+new_emulated(NewEmOpts, _) ->
+ NewEmOpts.
+
+ssl_options_list([], Acc) ->
+ lists:reverse(Acc);
+%% Skip internal options, only return user options
+ssl_options_list([{protocol, _}| T], Acc) ->
+ ssl_options_list(T, Acc);
+ssl_options_list([{erl_dist, _}|T], Acc) ->
+ ssl_options_list(T, Acc);
+ssl_options_list([{renegotiate_at, _}|T], Acc) ->
+ ssl_options_list(T, Acc);
+ssl_options_list([{max_fragment_length, _}|T], Acc) ->
+ %% skip max_fragment_length from options since it is taken above from connection_states
+ ssl_options_list(T, Acc);
+ssl_options_list([{ciphers = Key, Value}|T], Acc) ->
+ ssl_options_list(T,
+ [{Key, lists:map(
+ fun(Suite) ->
+ ssl_cipher_format:suite_bin_to_map(Suite)
+ end, Value)}
+ | Acc]);
+ssl_options_list([{Key, Value}|T], Acc) ->
+ ssl_options_list(T, [{Key, Value} | Acc]).
+
+%% Maybe add NSS keylog info according to
+%% https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format
+maybe_add_keylog(Info) ->
+ maybe_add_keylog(lists:keyfind(protocol, 1, Info), Info).
+
+maybe_add_keylog({_, 'tlsv1.2'}, Info) ->
+ try
+ {client_random, ClientRandomBin} = lists:keyfind(client_random, 1, Info),
+ {master_secret, MasterSecretBin} = lists:keyfind(master_secret, 1, Info),
+ ClientRandom = binary:decode_unsigned(ClientRandomBin),
+ MasterSecret = binary:decode_unsigned(MasterSecretBin),
+ Keylog = [io_lib:format("CLIENT_RANDOM ~64.16.0B ~96.16.0B", [ClientRandom, MasterSecret])],
+ Info ++ [{keylog,Keylog}]
+ catch
+ _Cxx:_Exx ->
+ Info
+ end;
+maybe_add_keylog({_, 'tlsv1.3'}, Info) ->
+ try
+ {client_random, ClientRandomBin} = lists:keyfind(client_random, 1, Info),
+ {client_traffic_secret_0, ClientTrafficSecret0Bin} = lists:keyfind(client_traffic_secret_0, 1, Info),
+ {server_traffic_secret_0, ServerTrafficSecret0Bin} = lists:keyfind(server_traffic_secret_0, 1, Info),
+ {client_handshake_traffic_secret, ClientHSecretBin} = lists:keyfind(client_handshake_traffic_secret, 1, Info),
+ {server_handshake_traffic_secret, ServerHSecretBin} = lists:keyfind(server_handshake_traffic_secret, 1, Info),
+ {selected_cipher_suite, #{prf := Prf}} = lists:keyfind(selected_cipher_suite, 1, Info),
+ ClientRandom = binary:decode_unsigned(ClientRandomBin),
+ ClientTrafficSecret0 = keylog_secret(ClientTrafficSecret0Bin, Prf),
+ ServerTrafficSecret0 = keylog_secret(ServerTrafficSecret0Bin, Prf),
+ ClientHSecret = keylog_secret(ClientHSecretBin, Prf),
+ ServerHSecret = keylog_secret(ServerHSecretBin, Prf),
+ Keylog = [io_lib:format("CLIENT_HANDSHAKE_TRAFFIC_SECRET ~64.16.0B ", [ClientRandom]) ++ ClientHSecret,
+ io_lib:format("SERVER_HANDSHAKE_TRAFFIC_SECRET ~64.16.0B ", [ClientRandom]) ++ ServerHSecret,
+ io_lib:format("CLIENT_TRAFFIC_SECRET_0 ~64.16.0B ", [ClientRandom]) ++ ClientTrafficSecret0,
+ io_lib:format("SERVER_TRAFFIC_SECRET_0 ~64.16.0B ", [ClientRandom]) ++ ServerTrafficSecret0],
+ Info ++ [{keylog,Keylog}]
+ catch
+ _Cxx:_Exx ->
+ Info
+ end;
+maybe_add_keylog(_, Info) ->
+ Info.
+
+keylog_secret(SecretBin, sha256) ->
+ io_lib:format("~64.16.0B", [binary:decode_unsigned(SecretBin)]);
+keylog_secret(SecretBin, sha384) ->
+ io_lib:format("~96.16.0B", [binary:decode_unsigned(SecretBin)]);
+keylog_secret(SecretBin, sha512) ->
+ io_lib:format("~128.16.0B", [binary:decode_unsigned(SecretBin)]).
+
+maybe_generate_client_shares(#{versions := [Version|_],
+ supported_groups :=
+ #supported_groups{
+ supported_groups = [Group|_]}})
+ when Version =:= {3,4} ->
+ %% Generate only key_share entry for the most preferred group
+ ssl_cipher:generate_client_shares([Group]);
+maybe_generate_client_shares(_) ->
+ undefined.
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index 4e2fdd1e8f..7d6c21438e 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -81,9 +81,10 @@
]).
-export([get_cert_params/1,
+ select_own_cert/1,
server_name/3,
validation_fun_and_state/4,
- handle_path_validation_error/7]).
+ path_validation_alert/1]).
%%====================================================================
%% Create handshake messages
@@ -125,30 +126,33 @@ server_hello_done() ->
#server_hello_done{}.
%%--------------------------------------------------------------------
--spec certificate(der_cert(), db_handle(), certdb_ref(), client | server) -> #certificate{} | #alert{}.
+-spec certificate([der_cert()] | undefined, db_handle(), certdb_ref(), client | server) -> #certificate{} | #alert{}.
%%
%% Description: Creates a certificate message.
%%--------------------------------------------------------------------
-certificate(OwnCert, CertDbHandle, CertDbRef, client) ->
+certificate(undefined, _, _, client) ->
+ %% If no suitable certificate is available, the client
+ %% SHOULD send a certificate message containing no
+ %% certificates. (chapter 7.4.6. RFC 4346)
+ #certificate{asn1_certificates = []};
+certificate([OwnCert], CertDbHandle, CertDbRef, client) ->
Chain =
case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of
{ok, _, CertChain} ->
CertChain;
{error, _} ->
- %% If no suitable certificate is available, the client
- %% SHOULD send a certificate message containing no
- %% certificates. (chapter 7.4.6. RFC 4346)
- []
- end,
+ certificate(undefined, CertDbHandle, CertDbRef, client)
+ end,
#certificate{asn1_certificates = Chain};
-
-certificate(OwnCert, CertDbHandle, CertDbRef, server) ->
+certificate([OwnCert], CertDbHandle, CertDbRef, server) ->
case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of
{ok, _, Chain} ->
#certificate{asn1_certificates = Chain};
{error, Error} ->
?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {server_has_no_suitable_certificates, Error})
- end.
+ end;
+certificate([_, _ |_] = Chain, _, _, _) ->
+ #certificate{asn1_certificates = Chain}.
%%--------------------------------------------------------------------
-spec client_certificate_verify(undefined | der_cert(), binary(),
@@ -162,7 +166,7 @@ client_certificate_verify(undefined, _, _, _, _, _) ->
ignore;
client_certificate_verify(_, _, _, _, undefined, _) ->
ignore;
-client_certificate_verify(OwnCert, MasterSecret, Version,
+client_certificate_verify([OwnCert|_], MasterSecret, Version,
{HashAlgo, SignAlgo},
PrivateKey, {Handshake, _}) ->
case public_key:pkix_is_fixed_dh_cert(OwnCert) of
@@ -344,46 +348,21 @@ next_protocol(SelectedProtocol) ->
%%--------------------------------------------------------------------
certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef,
#{server_name_indication := ServerNameIndication,
- partial_chain := PartialChain,
- verify_fun := VerifyFun,
- customize_hostname_check := CustomizeHostnameCheck,
- crl_check := CrlCheck,
- log_level := Level,
- signature_algs := SignAlgos,
- depth := Depth} = Opts, CRLDbHandle, Role, Host, Version,
- #{cert_ext := CertExt,
- ocsp_responder_certs := OcspResponderCerts,
- ocsp_state := OcspState}) ->
+ partial_chain := PartialChain} = SSlOptions,
+ CRLDbHandle, Role, Host, Version, CertExt) ->
ServerName = server_name(ServerNameIndication, Host, Role),
- [PeerCert | ChainCerts ] = ASN1Certs,
+ [PeerCert | _ChainCerts ] = ASN1Certs,
try
- {TrustedCert, CertPath} =
- ssl_certificate:trusted_cert_and_path(ASN1Certs, CertDbHandle, CertDbRef,
- PartialChain),
- ValidationFunAndState = validation_fun_and_state(VerifyFun, #{role => Role,
- certdb => CertDbHandle,
- certdb_ref => CertDbRef,
- server_name => ServerName,
- customize_hostname_check =>
- CustomizeHostnameCheck,
- signature_algs => SignAlgos,
- signature_algs_cert => undefined,
- version => Version,
- crl_check => CrlCheck,
- crl_db => CRLDbHandle,
- cert_ext => CertExt,
- issuer => TrustedCert,
- ocsp_responder_certs => OcspResponderCerts,
- ocsp_state => OcspState},
- CertPath, Level),
- Options = [{max_path_length, Depth},
- {verify_fun, ValidationFunAndState}],
- case public_key:pkix_path_validation(TrustedCert, CertPath, Options) of
+ PathsAndAnchors =
+ ssl_certificate:trusted_cert_and_paths(ASN1Certs, CertDbHandle, CertDbRef,
+ PartialChain),
+
+ case path_validate(PathsAndAnchors, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle,
+ Version, SSlOptions, CertExt) of
{ok, {PublicKeyInfo, _}} ->
{PeerCert, PublicKeyInfo};
{error, Reason} ->
- handle_path_validation_error(Reason, PeerCert, ChainCerts, Opts, Options,
- CertDbHandle, CertDbRef)
+ path_validation_alert(Reason)
end
catch
error:{_,{error, {asn1, Asn1Reason}}} ->
@@ -706,6 +685,11 @@ encode_extensions([#signature_algorithms_cert{
Len = ListLen + 2,
encode_extensions(Rest, <<?UINT16(?SIGNATURE_ALGORITHMS_CERT_EXT),
?UINT16(Len), ?UINT16(ListLen), SignSchemeList/binary, Acc/binary>>);
+encode_extensions([#sni{hostname = ""} | Rest], Acc) ->
+ HostnameBin = <<>>,
+ encode_extensions(Rest, <<?UINT16(?SNI_EXT), ?UINT16(0),
+ HostnameBin/binary,
+ Acc/binary>>);
encode_extensions([#sni{hostname = Hostname} | Rest], Acc) ->
HostLen = length(Hostname),
HostnameBin = list_to_binary(Hostname),
@@ -1640,6 +1624,11 @@ get_cert_params(Cert) ->
end,
{SignAlgo, Param, PublicKeyAlgo, RSAKeySize}.
+select_own_cert([OwnCert| _]) ->
+ OwnCert;
+select_own_cert(undefined) ->
+ undefined.
+
get_signature_scheme(undefined) ->
undefined;
get_signature_scheme(#signature_algorithms_cert{
@@ -1876,58 +1865,6 @@ maybe_check_hostname(OtpCert, valid_peer, SslState) ->
maybe_check_hostname(_, valid, _) ->
valid.
-handle_path_validation_error({bad_cert, unknown_ca} = Reason, PeerCert, Chain,
- Opts, Options, CertDbHandle, CertsDbRef) ->
- handle_incomplete_chain(PeerCert, Chain, Opts, Options, CertDbHandle, CertsDbRef, Reason);
-handle_path_validation_error({bad_cert, invalid_issuer} = Reason, PeerCert, Chain0,
- Opts, Options, CertDbHandle, CertsDbRef) ->
- handle_unordered_chain(PeerCert, Chain0, Opts, Options, CertDbHandle, CertsDbRef, Reason);
-handle_path_validation_error(Reason, _, _, _, _,_, _) ->
- path_validation_alert(Reason).
-
-handle_incomplete_chain(PeerCert, Chain0,
- #{partial_chain := PartialChain} = Opts, Options, CertDbHandle, CertsDbRef, Reason) ->
- case ssl_certificate:certificate_chain(PeerCert, CertDbHandle, CertsDbRef) of
- {ok, _, [PeerCert | _] = Chain} when Chain =/= Chain0 -> %% Chain candidate found
- case ssl_certificate:trusted_cert_and_path(Chain,
- CertDbHandle, CertsDbRef,
- PartialChain) of
- {unknown_ca, []} ->
- path_validation_alert(Reason);
- {Trusted, Path} ->
- case public_key:pkix_path_validation(Trusted, Path, Options) of
- {ok, {PublicKeyInfo,_}} ->
- {PeerCert, PublicKeyInfo};
- {error, PathError} ->
- handle_unordered_chain(PeerCert, Chain0, Opts, Options,
- CertDbHandle, CertsDbRef, PathError)
- end
- end;
- _ ->
- handle_unordered_chain(PeerCert, Chain0, Opts, Options, CertDbHandle, CertsDbRef, Reason)
- end.
-
-handle_unordered_chain(PeerCert, Chain0,
- #{partial_chain := PartialChain}, Options, CertDbHandle, CertsDbRef, Reason) ->
- {ok, ExtractedCerts} = ssl_pkix_db:extract_trusted_certs({der, Chain0}),
- case ssl_certificate:certificate_chain(PeerCert, CertDbHandle, ExtractedCerts, Chain0) of
- {ok, _, Chain} when Chain =/= Chain0 -> %% Chain appaears to be unordered
- case ssl_certificate:trusted_cert_and_path(Chain,
- CertDbHandle, CertsDbRef,
- PartialChain) of
- {unknown_ca, []} ->
- path_validation_alert(Reason);
- {Trusted, Path} ->
- case public_key:pkix_path_validation(Trusted, Path, Options) of
- {ok, {PublicKeyInfo,_}} ->
- {PeerCert, PublicKeyInfo};
- {error, PathError} ->
- path_validation_alert(PathError)
- end
- end;
- _ ->
- path_validation_alert(Reason)
- end.
path_validation_alert({bad_cert, cert_expired}) ->
?ALERT_REC(?FATAL, ?CERTIFICATE_EXPIRED);
@@ -3594,3 +3531,51 @@ empty_extensions(_, server_hello) ->
handle_log(Level, {LogLevel, ReportMap, Meta}) ->
ssl_logger:log(Level, LogLevel, ReportMap, Meta).
+
+
+path_validate([{TrustedCert, Path}], ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle,
+ Version, SslOptions, CertExt) ->
+ path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef,
+ CRLDbHandle, Version, SslOptions, CertExt);
+path_validate([{TrustedCert, Path} | Rest], ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle,
+ Version, SslOptions, CertExt) ->
+ case path_validation(TrustedCert, Path, ServerName,
+ Role, CertDbHandle, CRLDbHandle, CertDbRef,
+ Version, SslOptions, CertExt) of
+ {ok, _} = Result ->
+ Result;
+ {error, _} ->
+ path_validate(Rest, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle,
+ Version, SslOptions, CertExt)
+ end.
+
+path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, Version,
+ #{verify_fun := VerifyFun,
+ customize_hostname_check := CustomizeHostnameCheck,
+ crl_check := CrlCheck,
+ log_level := Level,
+ signature_algs := SignAlgos,
+ depth := Depth},
+ #{cert_ext := CertExt,
+ ocsp_responder_certs := OcspResponderCerts,
+ ocsp_state := OcspState}) ->
+ ValidationFunAndState =
+ validation_fun_and_state(VerifyFun, #{role => Role,
+ certdb => CertDbHandle,
+ certdb_ref => CertDbRef,
+ server_name => ServerName,
+ customize_hostname_check =>
+ CustomizeHostnameCheck,
+ signature_algs => SignAlgos,
+ signature_algs_cert => undefined,
+ version => Version,
+ crl_check => CrlCheck,
+ crl_db => CRLDbHandle,
+ cert_ext => CertExt,
+ issuer => TrustedCert,
+ ocsp_responder_certs => OcspResponderCerts,
+ ocsp_state => OcspState},
+ Path, Level),
+ Options = [{max_path_length, Depth},
+ {verify_fun, ValidationFunAndState}],
+ public_key:pkix_path_validation(TrustedCert, Path, Options).
diff --git a/lib/ssl/src/ssl_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl
index 6ce064fbed..790a115983 100644
--- a/lib/ssl/src/ssl_handshake.hrl
+++ b/lib/ssl/src/ssl_handshake.hrl
@@ -40,7 +40,7 @@
-record(session, {
session_id,
peer_certificate,
- own_certificate,
+ own_certificates,
compression_method,
cipher_suite,
master_secret,
diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl
index b9758ae763..6477f5ab57 100644
--- a/lib/ssl/src/ssl_internal.hrl
+++ b/lib/ssl/src/ssl_internal.hrl
@@ -137,7 +137,7 @@
crl_cache => {{ssl_crl_cache, {internal, []}}, [versions]},
crl_check => {false, [versions]},
customize_hostname_check => {[], [versions]},
- depth => {1, [versions]},
+ depth => {10, [versions]},
dh => {undefined, [versions]},
dhfile => {undefined, [versions]},
eccs => {undefined, [versions]},
@@ -175,6 +175,7 @@
reuse_session => {undefined, [versions]},
reuse_sessions => {true, [versions]},
secure_renegotiate => {true, [versions]},
+ keep_secrets => {false, [versions]},
server_name_indication => {undefined, [versions]},
session_tickets => {disabled, [versions]},
signature_algs => {undefined, [versions]},
diff --git a/lib/ssl/src/ssl_listen_tracker_sup.erl b/lib/ssl/src/ssl_listen_tracker_sup.erl
index f7e97bcb76..6afd1c0009 100644
--- a/lib/ssl/src/ssl_listen_tracker_sup.erl
+++ b/lib/ssl/src/ssl_listen_tracker_sup.erl
@@ -69,4 +69,4 @@ init(_O) ->
tracker_name(normal) ->
?MODULE;
tracker_name(dist) ->
- list_to_atom(atom_to_list(?MODULE) ++ "dist").
+ list_to_atom(atom_to_list(?MODULE) ++ "_dist").
diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl
index 5cc8dfaf79..4f6f12163f 100644
--- a/lib/ssl/src/ssl_manager.erl
+++ b/lib/ssl/src/ssl_manager.erl
@@ -515,14 +515,14 @@ exists_equivalent(_, []) ->
false;
exists_equivalent(#session{
peer_certificate = PeerCert,
- own_certificate = OwnCert,
+ own_certificates = [OwnCert | _],
compression_method = Compress,
cipher_suite = CipherSuite,
srp_username = SRP,
ecc = ECC} ,
[#session{
peer_certificate = PeerCert,
- own_certificate = OwnCert,
+ own_certificates = [OwnCert | _],
compression_method = Compress,
cipher_suite = CipherSuite,
srp_username = SRP,
diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl
index c19c6eeea9..47a9f11829 100644
--- a/lib/ssl/src/ssl_record.erl
+++ b/lib/ssl/src/ssl_record.erl
@@ -88,7 +88,8 @@ pending_connection_state(ConnectionStates, write) ->
maps:get(pending_write, ConnectionStates).
%%--------------------------------------------------------------------
--spec activate_pending_connection_state(connection_states(), read | write, tls_connection | dtls_connection) ->
+-spec activate_pending_connection_state(connection_states(), read | write,
+ tls_gen_connection | dtls_gen_connection) ->
connection_states().
%%
%% Description: Creates a new instance of the connection_states record
diff --git a/lib/ssl/src/ssl_server_session_cache.erl b/lib/ssl/src/ssl_server_session_cache.erl
index 022255258e..44862e5cad 100644
--- a/lib/ssl/src/ssl_server_session_cache.erl
+++ b/lib/ssl/src/ssl_server_session_cache.erl
@@ -66,6 +66,8 @@
{error, Error :: {already_started, pid()}} |
{error, Error :: term()} |
ignore.
+start_link(ssl_unknown_listener = Listner, Map) ->
+ gen_server:start_link({local, Listner}, ?MODULE, [Listner, Map], []);
start_link(Listner, Map) ->
gen_server:start_link(?MODULE, [Listner, Map], []).
@@ -113,7 +115,7 @@ init([Listner, #{lifetime := Lifetime,
max := Max
}]) ->
process_flag(trap_exit, true),
- erlang:monitor(process, Listner),
+ Monitor = monitor_listener(Listner),
DbRef = init(Cb, [{role, server} | InitArgs]),
State = #state{store_cb = Cb,
lifetime = Lifetime,
@@ -121,7 +123,7 @@ init([Listner, #{lifetime := Lifetime,
max = Max,
session_index = #{},
id_generator = crypto:strong_rand_bytes(16),
- listner = Listner
+ listner = Monitor
},
{ok, State}.
@@ -196,7 +198,7 @@ handle_cast({register_session, #session{session_id = SessionId} = Session},
-spec handle_info(Info :: timeout() | term(), State :: term()) ->
{noreply, NewState :: term()}.
-handle_info({'DOWN', _, process, Listner, _}, #state{listner = Listner} = State) ->
+handle_info({'DOWN', Monitor, _, _, _}, #state{listner = Monitor} = State) ->
{stop, normal, State};
handle_info(_, State) ->
{noreply, State}.
@@ -270,3 +272,10 @@ size(Cb,Cache) ->
error:undef ->
Cb:foldl(fun(_, Acc) -> Acc + 1 end, 0, Cache)
end.
+
+monitor_listener(ssl_unknown_listener) ->
+ %% Backwards compatible Erlang node
+ %% global process.
+ undefined;
+monitor_listener(Listen) when is_port(Listen) ->
+ erlang:monitor(port, Listen).
diff --git a/lib/ssl/src/ssl_server_session_cache_db.erl b/lib/ssl/src/ssl_server_session_cache_db.erl
index 090b139271..cab018c5c2 100644
--- a/lib/ssl/src/ssl_server_session_cache_db.erl
+++ b/lib/ssl/src/ssl_server_session_cache_db.erl
@@ -68,7 +68,7 @@ update(Cache, Key, Session) ->
%% Will only be called from the ssl_server_cache process.
%%--------------------------------------------------------------------
delete(Cache, Key) ->
- gb_trees:delete(Cache, Key).
+ gb_trees:delete(Key, Cache).
%%--------------------------------------------------------------
%% Description: Returns the cache size
diff --git a/lib/ssl/src/ssl_server_session_cache_sup.erl b/lib/ssl/src/ssl_server_session_cache_sup.erl
index ef8a24f19b..2f0c3dc823 100644
--- a/lib/ssl/src/ssl_server_session_cache_sup.erl
+++ b/lib/ssl/src/ssl_server_session_cache_sup.erl
@@ -29,30 +29,21 @@
-include("ssl_internal.hrl").
%% API
--export([start_link/0,
- start_link_dist/0]).
--export([start_child/1,
- start_child_dist/1,
- session_opts/0]).
+-export([start_link/0]).
+-export([start_child/1]).
%% Supervisor callback
-export([init/1]).
--define(DEFAULT_MAX_SESSION_CACHE, 1000).
%%%=========================================================================
%%% API
%%%=========================================================================
start_link() ->
- supervisor:start_link({local, tracker_name(normal)}, ?MODULE, []).
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
-start_link_dist() ->
- supervisor:start_link({local, tracker_name(dist)}, ?MODULE, []).
+start_child(Listner) ->
+ supervisor:start_child(?MODULE, [Listner | ssl_config:pre_1_3_session_opts()]).
-start_child(Args) ->
- supervisor:start_child(tracker_name(normal), [self() | Args]).
-
-start_child_dist(Args) ->
- supervisor:start_child(tracker_name(dist), [self() | Args]).
%%%=========================================================================
%%% Supervisor callback
@@ -72,45 +63,3 @@ init(_O) ->
ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules},
{ok, {{RestartStrategy, MaxR, MaxT}, [ChildSpec]}}.
-tracker_name(normal) ->
- ?MODULE;
-tracker_name(dist) ->
- list_to_atom(atom_to_list(?MODULE) ++ "dist").
-
-session_opts() ->
- CbOpts = case application:get_env(ssl, session_cb) of
- {ok, Cb} when is_atom(Cb) ->
- InitArgs = session_cb_init_args(),
- #{session_cb => Cb,
- session_cb_init_args => InitArgs};
- _ ->
- #{session_cb => ssl_server_session_cache_db,
- session_cb_init_args => []}
- end,
- LifeTime = session_lifetime(),
- Max = max_session_cache_size(),
- [CbOpts#{lifetime => LifeTime, max => Max}].
-
-session_cb_init_args() ->
- case application:get_env(ssl, session_cb_init_args) of
- {ok, Args} when is_list(Args) ->
- Args;
- _ ->
- []
- end.
-
-session_lifetime() ->
- case application:get_env(ssl, session_lifetime) of
- {ok, Time} when is_integer(Time) ->
- Time;
- _ ->
- ?'24H_in_sec'
- end.
-
-max_session_cache_size() ->
- case application:get_env(ssl, session_cache_server_max) of
- {ok, Size} when is_integer(Size) ->
- Size;
- _ ->
- ?DEFAULT_MAX_SESSION_CACHE
- end.
diff --git a/lib/ssl/src/ssl_session.erl b/lib/ssl/src/ssl_session.erl
index 1b04f88d12..ccc5c9ded7 100644
--- a/lib/ssl/src/ssl_session.erl
+++ b/lib/ssl/src/ssl_session.erl
@@ -30,11 +30,22 @@
-include("ssl_api.hrl").
%% Internal application API
--export([is_new/2, client_select_session/4, server_select_session/5, valid_session/2]).
+-export([is_new/2, client_select_session/4, server_select_session/5, valid_session/2, legacy_session_id/0]).
-type seconds() :: integer().
%%--------------------------------------------------------------------
+-spec legacy_session_id() -> ssl:session_id().
+%%
+%% Description: TLS-1.3 deprecates the session id but has a dummy
+%% value for it for protocol backwards-compatibility reasons.
+%% If now lower versions are configured this function can be called
+%% for a dummy value.
+%%--------------------------------------------------------------------
+legacy_session_id() ->
+ crypto:strong_rand_bytes(32).
+
+%%--------------------------------------------------------------------
-spec is_new(ssl:session_id(), ssl:session_id()) -> boolean().
%%
%% Description: Checks if the session id decided by the server is a
@@ -63,7 +74,7 @@ client_select_session({_, _, #{versions := Versions,
case Version of
{3, N} when N >= 4 ->
- NewSession#session{session_id = crypto:strong_rand_bytes(32)};
+ NewSession#session{session_id = legacy_session_id()};
_ ->
do_client_select_session(ClientInfo, Cache, CacheCb, NewSession)
end.
@@ -100,7 +111,15 @@ valid_session(#session{time_stamp = TimeStamp}, LifeTime) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-
+do_client_select_session({_, _, #{reuse_session := {SessionId, SessionData}}}, _, _, NewSession) when is_binary(SessionId) andalso
+ is_binary(SessionData) ->
+ try binary_to_term(SessionData, [safe]) of
+ Session ->
+ Session
+ catch
+ _:_ ->
+ NewSession#session{session_id = <<>>}
+ end;
do_client_select_session({Host, Port, #{reuse_session := SessionId}}, Cache, CacheCb, NewSession) when is_binary(SessionId)->
case CacheCb:lookup(Cache, {{Host, Port}, SessionId}) of
undefined ->
@@ -109,8 +128,8 @@ do_client_select_session({Host, Port, #{reuse_session := SessionId}}, Cache, Cac
Session
end;
do_client_select_session(ClientInfo,
- Cache, CacheCb, #session{own_certificate = OwnCert} = NewSession) ->
- case select_session(ClientInfo, Cache, CacheCb, OwnCert) of
+ Cache, CacheCb, #session{own_certificates = OwnCerts} = NewSession) ->
+ case select_session(ClientInfo, Cache, CacheCb, OwnCerts) of
no_session ->
NewSession#session{session_id = <<>>};
Session ->
@@ -120,18 +139,18 @@ do_client_select_session(ClientInfo,
select_session({_, _, #{reuse_sessions := Reuse}}, _Cache, _CacheCb, _OwnCert) when Reuse =/= true ->
%% If reuse_sessions == false | save a new session should be created
no_session;
-select_session({HostIP, Port, SslOpts}, Cache, CacheCb, OwnCert) ->
+select_session({HostIP, Port, SslOpts}, Cache, CacheCb, OwnCerts) ->
Sessions = CacheCb:select_session(Cache, {HostIP, Port}),
- select_session(Sessions, SslOpts, OwnCert).
+ select_session(Sessions, SslOpts, OwnCerts).
select_session([], _, _) ->
no_session;
-select_session(Sessions, #{ciphers := Ciphers}, OwnCert) ->
+select_session(Sessions, #{ciphers := Ciphers}, OwnCerts) ->
IsNotResumable =
fun(Session) ->
not (resumable(Session#session.is_resumable) andalso
lists:member(Session#session.cipher_suite, Ciphers)
- andalso (OwnCert == Session#session.own_certificate))
+ andalso (OwnCerts == Session#session.own_certificates))
end,
case lists:dropwhile(IsNotResumable, Sessions) of
[] -> no_session;
@@ -143,7 +162,7 @@ is_resumable(_, _, #{reuse_sessions := false}, _) ->
is_resumable(SuggestedSessionId, SessIdTracker, #{reuse_session := ReuseFun} = Options, OwnCert) ->
case ssl_server_session_cache:reuse_session(SessIdTracker, SuggestedSessionId) of
#session{cipher_suite = CipherSuite,
- own_certificate = SessionOwnCert,
+ own_certificates = [SessionOwnCert | _],
compression_method = Compression,
is_resumable = IsResumable,
peer_certificate = PeerCert} = Session ->
diff --git a/lib/ssl/src/ssl_session_cache.erl b/lib/ssl/src/ssl_session_cache.erl
index a8a1c3ed95..b2b33b9af3 100644
--- a/lib/ssl/src/ssl_session_cache.erl
+++ b/lib/ssl/src/ssl_session_cache.erl
@@ -42,7 +42,7 @@ terminate(Cache) ->
ets:delete(Cache).
%%--------------------------------------------------------------------
-%% Description: Looks up a cach entry. Should be callable from any
+%% Description: Looks up a cache entry. Should be callable from any
%% process.
%%--------------------------------------------------------------------
lookup(Cache, Key) ->
@@ -64,7 +64,7 @@ update(Cache, Key, Session) ->
ets:insert(Cache, {Key, Session}).
%%--------------------------------------------------------------------
-%% Description: Delets a cache entry.
+%% Description: Deletes a cache entry.
%% Will only be called from the ssl_manager process.
%%--------------------------------------------------------------------
delete(Cache, Key) ->
diff --git a/lib/ssl/src/ssl_upgrade_server_session_cache_sup.erl b/lib/ssl/src/ssl_upgrade_server_session_cache_sup.erl
new file mode 100644
index 0000000000..936ffcc0ac
--- /dev/null
+++ b/lib/ssl/src/ssl_upgrade_server_session_cache_sup.erl
@@ -0,0 +1,90 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2020-2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Supervisor for a listen options tracker
+%%----------------------------------------------------------------------
+-module(ssl_upgrade_server_session_cache_sup).
+
+-behaviour(supervisor).
+
+-include("ssl_internal.hrl").
+
+%% API
+-export([start_link/0,
+ start_link_dist/0]).
+-export([start_child/1]).
+
+%% Supervisor callback
+-export([init/1]).
+
+%%%=========================================================================
+%%% API
+%%%=========================================================================
+start_link() ->
+ supervisor:start_link({local, sup_name(normal)}, ?MODULE, []).
+
+start_link_dist() ->
+ supervisor:start_link({local, sup_name(dist)}, ?MODULE, []).
+
+start_child(Type) ->
+ SupName = sup_name(Type),
+ Children = supervisor:count_children(SupName),
+ Workers = proplists:get_value(workers, Children),
+ case Workers of
+ 0 ->
+ %% In case two upgrade servers are started very close to each other
+ %% only one will be able to grab the local name and we will use
+ %% that process for handling pre TLS-1.3 sessions for
+ %% servers with to us unknown listeners.
+ case supervisor:start_child(SupName, [ssl_unknown_listener | ssl_config:pre_1_3_session_opts()]) of
+ {error, {already_started, Child}} ->
+ {ok, Child};
+ {ok, _} = Return ->
+ Return
+ end;
+ 1 ->
+ [{_,Child,_, _}] = supervisor:which_children(SupName),
+ {ok, Child}
+ end.
+
+%%%=========================================================================
+%%% Supervisor callback
+%%%=========================================================================
+init(_O) ->
+ RestartStrategy = simple_one_for_one,
+ MaxR = 3,
+ MaxT = 3600,
+
+ Name = undefined, % As simple_one_for_one is used.
+ StartFunc = {ssl_server_session_cache, start_link, []},
+ Restart = transient, % Should be restarted only on abnormal termination
+ Shutdown = 4000,
+ Modules = [ssl_server_session_cache],
+ Type = worker,
+
+ ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules},
+ {ok, {{RestartStrategy, MaxR, MaxT}, [ChildSpec]}}.
+
+sup_name(normal) ->
+ ?MODULE;
+sup_name(dist) ->
+ list_to_atom(atom_to_list(?MODULE) ++ "_dist").
diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl
index c13e4fd022..e96e0e2cf6 100644
--- a/lib/ssl/src/tls_connection.erl
+++ b/lib/ssl/src/tls_connection.erl
@@ -19,399 +19,144 @@
%%
%%
%%----------------------------------------------------------------------
-%% Purpose: Handles an ssl connection, e.i. both the setup
-%% e.i. SSL-Handshake, SSL-Alert and SSL-Cipher protocols and delivering
-%% data to the application. All data on the connectinon is received and
-%% sent according to the SSL-record protocol.
+%% Purpose: TLS-1.0-TLS-1.2 FSM (* = optional)
+%% %%----------------------------------------------------------------------
+%% TLS Handshake protocol full Handshake
+%% Client Server
+%%
+%% ClientHello --------> Flight 1
+%% ServerHello \
+%% Certificate* \
+%% ServerKeyExchange* Flight 2
+%% CertificateRequest* /
+%% <-------- ServerHelloDone /
+%% Certificate* \
+%% ClientKeyExchange \
+%% CertificateVerify* Flight 3 part 1
+%% [ChangeCipherSpec] /
+%% Finished --------> / Flight 3 part 2
+%% [ChangeCipherSpec]
+%% <-------- Finished Flight 4
+%% Application Data <-------> Application Data
+%%
+%%
+%% TLS Handshake protocol abbreviated Handshake
+%% Client Server
+%%
+%% ClientHello --------> Abbrev Flight 1
+%% ServerHello Abbrev Flight 2 part 1
+%% [ChangeCipherSpec]
+%% <-------- Finished Abbrev Flight 2 part 2
+%% [ChangeCipherSpec]
+%% Finished --------> Abbrev Flight 3
+%% Application Data <-------> Application Data
+%%
+%%
+%%
+%% Start FSM ---> CONFIG_ERROR
+%% Send error to user
+%% | and shutdown
+%% |
+%% V
+%% INITIAL_HELLO
+%%
+%% | Send/Recv Flight 1
+%% |
+%% |
+%% USER_HELLO |
+%% <- Possibly let user provide V
+%% options after looking at hello ex -> HELLO
+%% | Send/Recv Flight 2 or Abbrev Flight 1 - Abbrev Flight 2 part 1
+%% |
+%% New session | Resumed session
+%% WAIT_OCSP_STAPELING CERTIFY <----------------------------------> ABBRIVIATED
+%%
+%% <- Possibly Receive -- | |
+%% OCSP Stapel ------> | Flight 3 part 1 |
+%% | |
+%% V | Abbrev Flight 2 part 2 to Abbrev Flight 3
+%% CIPHER |
+%% | |
+%% | Fligth 3 part 2 to Flight 4 |
+%% | |
+%% V V
+%% ----------------------------------------------------
+%% |
+%% |
+%% V
+%% CONNECTION
+%% |
+%% | Renegotiaton
+%% V
+%% GO BACK TO HELLO
%%----------------------------------------------------------------------
-module(tls_connection).
-behaviour(gen_statem).
+-include_lib("public_key/include/public_key.hrl").
+-include_lib("kernel/include/logger.hrl").
+
-include("tls_connection.hrl").
-include("tls_handshake.hrl").
--include("tls_handshake_1_3.hrl").
-include("ssl_alert.hrl").
-include("tls_record.hrl").
-include("ssl_cipher.hrl").
-include("ssl_api.hrl").
-include("ssl_internal.hrl").
--include("ssl_srp.hrl").
--include_lib("public_key/include/public_key.hrl").
--include_lib("kernel/include/logger.hrl").
%% Internal application API
%% Setup
--export([start_fsm/8, start_link/8, init/1, pids/1]).
-
-%% State transition handling
--export([next_event/3, next_event/4,
- handle_protocol_record/3]).
+-export([init/1]).
-%% Handshake handling
--export([renegotiation/2, renegotiate/2, send_handshake/2,
- send_handshake_flight/1,
- queue_handshake/2, queue_change_cipher/2,
- reinit/1, reinit_handshake_data/1, select_sni_extension/1,
- empty_connection_state/2]).
-
-%% Alert and close handling
--export([send_alert/2, send_alert_in_connection/2,
- send_sync_alert/2,
- close/5, protocol_name/0]).
-
-%% Data handling
--export([socket/4, setopts/3, getopts/3]).
+-export([renegotiate/2]).
%% gen_statem state functions
--export([init/3, error/3, downgrade/3, %% Initiation and take down states
- hello/3, user_hello/3, wait_ocsp_stapling/3, certify/3, cipher/3, abbreviated/3, %% Handshake states
+-export([initial_hello/3,
+ config_error/3,
+ downgrade/3,
+ hello/3,
+ user_hello/3,
+ wait_ocsp_stapling/3,
+ certify/3,
+ cipher/3,
+ abbreviated/3,
connection/3]).
-%% TLS 1.3 state functions (server)
--export([start/3, %% common state with client
- negotiated/3,
- recvd_ch/3,
- wait_cert/3, %% common state with client
- wait_cv/3, %% common state with client
- wait_eoed/3,
- wait_finished/3, %% common state with client
- wait_flight2/3,
- connected/3 %% common state with client
- ]).
-%% TLS 1.3 state functions (client)
--export([wait_cert_cr/3,
- wait_ee/3,
- wait_sh/3
- ]).
+
%% gen_statem callbacks
--export([callback_mode/0, terminate/3, code_change/4, format_status/2]).
+-export([callback_mode/0,
+ terminate/3,
+ code_change/4,
+ format_status/2]).
--export([encode_handshake/4, send_key_update/2, update_cipher_key/2]).
-
--define(DIST_CNTRL_SPAWN_OPTS, [{priority, max}]).
-
%%====================================================================
%% Internal application API
%%====================================================================
-%%====================================================================
-%% Setup
-%%====================================================================
-start_fsm(Role, Host, Port, Socket, {#{erl_dist := false},_, Trackers} = Opts,
- User, {CbModule, _,_, _, _} = CbInfo,
- Timeout) ->
- try
- {ok, Sender} = tls_sender:start(),
- {ok, Pid} = tls_connection_sup:start_child([Role, Sender, Host, Port, Socket,
- Opts, User, CbInfo]),
- {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, [Pid, Sender], CbModule, Trackers),
- ssl_connection:handshake(SslSocket, Timeout)
- catch
- error:{badmatch, {error, _} = Error} ->
- Error
- end;
-
-start_fsm(Role, Host, Port, Socket, {#{erl_dist := true},_, Trackers} = Opts,
- User, {CbModule, _,_, _, _} = CbInfo,
- Timeout) ->
- try
- {ok, Sender} = tls_sender:start([{spawn_opt, ?DIST_CNTRL_SPAWN_OPTS}]),
- {ok, Pid} = tls_connection_sup:start_child_dist([Role, Sender, Host, Port, Socket,
- Opts, User, CbInfo]),
- {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, [Pid, Sender], CbModule, Trackers),
- ssl_connection:handshake(SslSocket, Timeout)
- catch
- error:{badmatch, {error, _} = Error} ->
- Error
- end.
-
-%%--------------------------------------------------------------------
--spec start_link(atom(), pid(), ssl:host(), inet:port_number(), port(), list(), pid(), tuple()) ->
- {ok, pid()} | ignore | {error, reason()}.
-%%
-%% Description: Creates a gen_statem process which calls Module:init/1 to
-%% initialize.
-%%--------------------------------------------------------------------
-start_link(Role, Sender, Host, Port, Socket, Options, User, CbInfo) ->
- {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Sender, Host, Port, Socket, Options, User, CbInfo]])}.
-
-init([Role, Sender, Host, Port, Socket, {#{erl_dist := ErlDist}, _, _} = Options, User, CbInfo]) ->
- process_flag(trap_exit, true),
- link(Sender),
- case ErlDist of
- true ->
- process_flag(priority, max);
- _ ->
- ok
- end,
+init([Role, Sender, Host, Port, Socket, Options, User, CbInfo]) ->
State0 = #state{protocol_specific = Map} = initial_state(Role, Sender,
Host, Port, Socket, Options, User, CbInfo),
try
- State = ssl_connection:ssl_config(State0#state.ssl_options, Role, State0),
- initialize_tls_sender(State),
- gen_statem:enter_loop(?MODULE, [], init, State)
+ State1 = #state{static_env = #static_env{session_cache = Cache,
+ session_cache_cb = CacheCb
+ },
+ ssl_options = SslOptions,
+ session = Session0} = ssl_gen_statem:ssl_config(State0#state.ssl_options, Role, State0),
+ State = case Role of
+ client ->
+ Session = ssl_session:client_select_session({Host, Port, SslOptions}, Cache, CacheCb, Session0),
+ State1#state{session = Session};
+ server ->
+ State1
+ end,
+ tls_gen_connection:initialize_tls_sender(State),
+ gen_statem:enter_loop(?MODULE, [], initial_hello, State)
catch throw:Error ->
EState = State0#state{protocol_specific = Map#{error => Error}},
- gen_statem:enter_loop(?MODULE, [], error, EState)
- end.
-
-pids(#state{protocol_specific = #{sender := Sender}}) ->
- [self(), Sender].
-
-%%====================================================================
-%% State transition handling
-%%====================================================================
-next_record(_, #state{handshake_env =
- #handshake_env{unprocessed_handshake_events = N} = HsEnv}
- = State) when N > 0 ->
- {no_record, State#state{handshake_env =
- HsEnv#handshake_env{unprocessed_handshake_events = N-1}}};
-next_record(_, #state{protocol_buffers =
- #protocol_buffers{tls_cipher_texts = [_|_] = CipherTexts},
- connection_states = ConnectionStates,
- ssl_options = #{padding_check := Check}} = State) ->
- next_record(State, CipherTexts, ConnectionStates, Check);
-next_record(connection, #state{protocol_buffers = #protocol_buffers{tls_cipher_texts = []},
- protocol_specific = #{active_n_toggle := true}
- } = State) ->
- %% If ssl application user is not reading data wait to activate socket
- flow_ctrl(State);
-
-next_record(_, #state{protocol_buffers = #protocol_buffers{tls_cipher_texts = []},
- protocol_specific = #{active_n_toggle := true}
- } = State) ->
- activate_socket(State);
-next_record(_, State) ->
- {no_record, State}.
-
-%%% bytes_to_read equals the integer Length arg of ssl:recv
-%%% the actual value is only relevant for packet = raw | 0
-%%% bytes_to_read = undefined means no recv call is ongoing
-flow_ctrl(#state{user_data_buffer = {_,Size,_},
- socket_options = #socket_options{active = false},
- bytes_to_read = undefined} = State) when Size =/= 0 ->
- %% Passive mode wait for new recv request or socket activation
- %% that is preserv some tcp back pressure by waiting to activate
- %% socket
- {no_record, State};
-%%%%%%%%%% A packet mode is set and socket is passive %%%%%%%%%%
-flow_ctrl(#state{socket_options = #socket_options{active = false,
- packet = Packet}} = State)
- when ((Packet =/= 0) andalso (Packet =/= raw)) ->
- %% We need more data to complete the packet.
- activate_socket(State);
-%%%%%%%%% No packet mode set and socket is passive %%%%%%%%%%%%
-flow_ctrl(#state{user_data_buffer = {_,Size,_},
- socket_options = #socket_options{active = false},
- bytes_to_read = 0} = State) when Size == 0 ->
- %% Passive mode no available bytes, get some
- activate_socket(State);
-flow_ctrl(#state{user_data_buffer = {_,Size,_},
- socket_options = #socket_options{active = false},
- bytes_to_read = 0} = State) when Size =/= 0 ->
- %% There is data in the buffer to deliver
- {no_record, State};
-flow_ctrl(#state{user_data_buffer = {_,Size,_},
- socket_options = #socket_options{active = false},
- bytes_to_read = BytesToRead} = State) when (BytesToRead > 0) ->
- case (Size >= BytesToRead) of
- true -> %% There is enough data bufferd
- {no_record, State};
- false -> %% We need more data to complete the delivery of <BytesToRead> size
- activate_socket(State)
- end;
-%%%%%%%%%%% Active mode or more data needed %%%%%%%%%%
-flow_ctrl(State) ->
- activate_socket(State).
-
-
-activate_socket(#state{protocol_specific = #{active_n_toggle := true, active_n := N} = ProtocolSpec,
- static_env = #static_env{socket = Socket,
- close_tag = CloseTag,
- transport_cb = Transport}
- } = State) ->
- case tls_socket:setopts(Transport, Socket, [{active, N}]) of
- ok ->
- {no_record, State#state{protocol_specific = ProtocolSpec#{active_n_toggle => false}}};
- _ ->
- self() ! {CloseTag, Socket},
- {no_record, State}
+ gen_statem:enter_loop(?MODULE, [], config_error, EState)
end.
-%% Decipher next record and concatenate consecutive ?APPLICATION_DATA records into one
-%%
-next_record(State, CipherTexts, ConnectionStates, Check) ->
- next_record(State, CipherTexts, ConnectionStates, Check, []).
-%%
-next_record(#state{connection_env = #connection_env{negotiated_version = {3,4} = Version}} = State,
- [CT|CipherTexts], ConnectionStates0, Check, Acc) ->
- case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of
- {#ssl_tls{type = ?APPLICATION_DATA, fragment = Fragment}, ConnectionStates} ->
- case CipherTexts of
- [] ->
- %% End of cipher texts - build and deliver an ?APPLICATION_DATA record
- %% from the accumulated fragments
- next_record_done(State, [], ConnectionStates,
- #ssl_tls{type = ?APPLICATION_DATA,
- fragment = iolist_to_binary(lists:reverse(Acc, [Fragment]))});
- [_|_] ->
- next_record(State, CipherTexts, ConnectionStates, Check, [Fragment|Acc])
- end;
- {Record, ConnectionStates} when Acc =:= [] ->
- %% Singelton non-?APPLICATION_DATA record - deliver
- next_record_done(State, CipherTexts, ConnectionStates, Record);
- {_Record, _ConnectionStates_to_forget} ->
- %% Not ?APPLICATION_DATA but we have accumulated fragments
- %% -> build an ?APPLICATION_DATA record with concatenated fragments
- %% and forget about decrypting this record - we'll decrypt it again next time
- %% Will not work for stream ciphers
- next_record_done(State, [CT|CipherTexts], ConnectionStates0,
- #ssl_tls{type = ?APPLICATION_DATA, fragment = iolist_to_binary(lists:reverse(Acc))});
- #alert{} = Alert ->
- Alert
- end;
-next_record(#state{connection_env = #connection_env{negotiated_version = Version}} = State,
- [#ssl_tls{type = ?APPLICATION_DATA} = CT |CipherTexts], ConnectionStates0, Check, Acc) ->
- case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of
- {#ssl_tls{type = ?APPLICATION_DATA, fragment = Fragment}, ConnectionStates} ->
- case CipherTexts of
- [] ->
- %% End of cipher texts - build and deliver an ?APPLICATION_DATA record
- %% from the accumulated fragments
- next_record_done(State, [], ConnectionStates,
- #ssl_tls{type = ?APPLICATION_DATA,
- fragment = iolist_to_binary(lists:reverse(Acc, [Fragment]))});
- [_|_] ->
- next_record(State, CipherTexts, ConnectionStates, Check, [Fragment|Acc])
- end;
- #alert{} = Alert ->
- Alert
- end;
-next_record(State, CipherTexts, ConnectionStates, _, [_|_] = Acc) ->
- next_record_done(State, CipherTexts, ConnectionStates,
- #ssl_tls{type = ?APPLICATION_DATA,
- fragment = iolist_to_binary(lists:reverse(Acc))});
-next_record(#state{connection_env = #connection_env{negotiated_version = Version}} = State,
- [CT|CipherTexts], ConnectionStates0, Check, []) ->
- case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of
- {Record, ConnectionStates} ->
- %% Singelton non-?APPLICATION_DATA record - deliver
- next_record_done(State, CipherTexts, ConnectionStates, Record);
- #alert{} = Alert ->
- Alert
- end.
-
-next_record_done(#state{protocol_buffers = Buffers} = State, CipherTexts, ConnectionStates, Record) ->
- {Record,
- State#state{protocol_buffers = Buffers#protocol_buffers{tls_cipher_texts = CipherTexts},
- connection_states = ConnectionStates}}.
-
-next_event(StateName, Record, State) ->
- next_event(StateName, Record, State, []).
-%%
-next_event(StateName, no_record, #state{static_env = #static_env{role = Role}} = State0, Actions) ->
- case next_record(StateName, State0) of
- {no_record, State} ->
- ssl_connection:hibernate_after(StateName, State, Actions);
- {Record, State} ->
- next_event(StateName, Record, State, Actions);
- #alert{} = Alert ->
- ssl_connection:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State0),
- {stop, {shutdown, own_alert}, State0}
- end;
-next_event(StateName, #ssl_tls{} = Record, State, Actions) ->
- {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]};
-next_event(StateName, #alert{} = Alert, State, Actions) ->
- {next_state, StateName, State, [{next_event, internal, Alert} | Actions]}.
-
-%%% TLS record protocol level application data messages
-handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName,
- #state{start_or_recv_from = From,
- socket_options = #socket_options{active = false}} = State0) when From =/= undefined ->
- case ssl_connection:read_application_data(Data, State0) of
- {stop, _, _} = Stop->
- Stop;
- {Record, #state{start_or_recv_from = Caller} = State} ->
- TimerAction = case Caller of
- undefined -> %% Passive recv complete cancel timer
- [{{timeout, recv}, infinity, timeout}];
- _ ->
- []
- end,
- next_event(StateName, Record, State, TimerAction)
- end;
-handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName, State0) ->
- case ssl_connection:read_application_data(Data, State0) of
- {stop, _, _} = Stop->
- Stop;
- {Record, State} ->
- next_event(StateName, Record, State)
- end;
-%%% TLS record protocol level handshake messages
-handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, fragment = Data},
- StateName, #state{protocol_buffers =
- #protocol_buffers{tls_handshake_buffer = Buf0} = Buffers,
- connection_env = #connection_env{negotiated_version = Version},
- static_env = #static_env{role = Role},
- ssl_options = Options} = State0) ->
- try
- %% Calculate the effective version that should be used when decoding an incoming handshake
- %% message.
- EffectiveVersion = effective_version(Version, Options, Role),
- {Packets, Buf} = tls_handshake:get_tls_handshake(EffectiveVersion,Data,Buf0, Options),
- State =
- State0#state{protocol_buffers =
- Buffers#protocol_buffers{tls_handshake_buffer = Buf}},
- case Packets of
- [] ->
- assert_buffer_sanity(Buf, Options),
- next_event(StateName, no_record, State);
- _ ->
- Events = tls_handshake_events(Packets),
- case StateName of
- connection ->
- ssl_connection:hibernate_after(StateName, State, Events);
- _ ->
- HsEnv = State#state.handshake_env,
- {next_state, StateName,
- State#state{handshake_env =
- HsEnv#handshake_env{unprocessed_handshake_events
- = unprocessed_events(Events)}}, Events}
- end
- end
- catch throw:#alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, Version, StateName, State0)
- end;
-%%% TLS record protocol level change cipher messages
-handle_protocol_record(#ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, StateName, State) ->
- {next_state, StateName, State, [{next_event, internal, #change_cipher_spec{type = Data}}]};
-%%% TLS record protocol level Alert messages
-handle_protocol_record(#ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName,
- #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
- try decode_alerts(EncAlerts) of
- Alerts = [_|_] ->
- handle_alerts(Alerts, {next_state, StateName, State});
- [] ->
- ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, empty_alert),
- Version, StateName, State);
- #alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, Version, StateName, State)
- catch
- _:_ ->
- ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, alert_decode_error),
- Version, StateName, State)
-
- end;
-%% Ignore unknown TLS record level protocol messages
-handle_protocol_record(#ssl_tls{type = _Unknown}, StateName, State) ->
- {next_state, StateName, State, []}.
-%%====================================================================
-%% Handshake handling
-%%====================================================================
-renegotiation(Pid, WriteState) ->
- gen_statem:call(Pid, {user_renegotiate, WriteState}).
-
renegotiate(#state{static_env = #static_env{role = client},
handshake_env = HsEnv} = State, Actions) ->
%% Handle same way as if server requested
@@ -434,249 +179,26 @@ renegotiate(#state{static_env = #static_env{role = server,
State = State0#state{connection_states =
ConnectionStates,
handshake_env = HsEnv#handshake_env{tls_handshake_history = Hs0}},
- next_event(hello, no_record, State, Actions).
-
-send_handshake(Handshake, State) ->
- send_handshake_flight(queue_handshake(Handshake, State)).
-
-queue_handshake(Handshake, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv,
- connection_env = #connection_env{negotiated_version = Version},
- flight_buffer = Flight0,
- ssl_options = #{log_level := LogLevel},
- connection_states = ConnectionStates0} = State0) ->
- {BinHandshake, ConnectionStates, Hist} =
- encode_handshake(Handshake, Version, ConnectionStates0, Hist0),
- ssl_logger:debug(LogLevel, outbound, 'handshake', Handshake),
- ssl_logger:debug(LogLevel, outbound, 'record', BinHandshake),
-
- State0#state{connection_states = ConnectionStates,
- handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist},
- flight_buffer = Flight0 ++ [BinHandshake]}.
-
--spec send_handshake_flight(StateIn) -> {StateOut, FlightBuffer} when
- StateIn :: #state{},
- StateOut :: #state{},
- FlightBuffer :: list().
-send_handshake_flight(#state{static_env = #static_env{socket = Socket,
- transport_cb = Transport},
- flight_buffer = Flight} = State0) ->
- tls_socket:send(Transport, Socket, Flight),
- {State0#state{flight_buffer = []}, []}.
-
-
-queue_change_cipher(Msg, #state{connection_env = #connection_env{negotiated_version = Version},
- flight_buffer = Flight0,
- ssl_options = #{log_level := LogLevel},
- connection_states = ConnectionStates0} = State0) ->
- {BinChangeCipher, ConnectionStates} =
- encode_change_cipher(Msg, Version, ConnectionStates0),
- ssl_logger:debug(LogLevel, outbound, 'record', BinChangeCipher),
- State0#state{connection_states = ConnectionStates,
- flight_buffer = Flight0 ++ [BinChangeCipher]}.
-
-reinit(#state{protocol_specific = #{sender := Sender},
- connection_env = #connection_env{negotiated_version = Version},
- connection_states = #{current_write := Write}} = State) ->
- tls_sender:update_connection_state(Sender, Write, Version),
- reinit_handshake_data(State).
-
-reinit_handshake_data(#state{handshake_env = HsEnv} =State) ->
- %% premaster_secret, public_key_info and tls_handshake_info
- %% are only needed during the handshake phase.
- %% To reduce memory foot print of a connection reinitialize them.
- State#state{
- handshake_env = HsEnv#handshake_env{tls_handshake_history = ssl_handshake:init_handshake_history(),
- public_key_info = undefined,
- premaster_secret = undefined}
- }.
-
-select_sni_extension(#client_hello{extensions = #{sni := SNI}}) ->
- SNI;
-select_sni_extension(_) ->
- undefined.
-
-empty_connection_state(ConnectionEnd, BeastMitigation) ->
- ssl_record:empty_connection_state(ConnectionEnd, BeastMitigation).
-
-%%====================================================================
-%% Alert and close handling
-%%====================================================================
-
-%%--------------------------------------------------------------------
--spec encode_alert(#alert{}, ssl_record:ssl_version(), ssl_record:connection_states()) ->
- {iolist(), ssl_record:connection_states()}.
-%%
-%% Description: Encodes an alert
-%%--------------------------------------------------------------------
-encode_alert(#alert{} = Alert, Version, ConnectionStates) ->
- tls_record:encode_alert_record(Alert, Version, ConnectionStates).
-
-send_alert(Alert, #state{static_env = #static_env{socket = Socket,
- transport_cb = Transport},
- connection_env = #connection_env{negotiated_version = Version},
- ssl_options = #{log_level := LogLevel},
- connection_states = ConnectionStates0} = StateData0) ->
- {BinMsg, ConnectionStates} =
- encode_alert(Alert, Version, ConnectionStates0),
- tls_socket:send(Transport, Socket, BinMsg),
- ssl_logger:debug(LogLevel, outbound, 'record', BinMsg),
- StateData0#state{connection_states = ConnectionStates}.
-
-%% If an ALERT sent in the connection state, should cause the TLS
-%% connection to end, we need to synchronize with the tls_sender
-%% process so that the ALERT if possible (that is the tls_sender process is
-%% not blocked) is sent before the connection process terminates and
-%% thereby closes the transport socket.
-send_alert_in_connection(#alert{level = ?FATAL} = Alert, State) ->
- send_sync_alert(Alert, State);
-send_alert_in_connection(#alert{description = ?CLOSE_NOTIFY} = Alert, State) ->
- send_sync_alert(Alert, State);
-send_alert_in_connection(Alert,
- #state{protocol_specific = #{sender := Sender}}) ->
- tls_sender:send_alert(Sender, Alert).
-send_sync_alert(
- Alert, #state{protocol_specific = #{sender := Sender}} = State) ->
- try tls_sender:send_and_ack_alert(Sender, Alert)
- catch
- _:_ ->
- throw({stop, {shutdown, own_alert}, State})
- end.
-
-%% User closes or recursive call!
-close({close, Timeout}, Socket, Transport = gen_tcp, _,_) ->
- tls_socket:setopts(Transport, Socket, [{active, false}]),
- Transport:shutdown(Socket, write),
- _ = Transport:recv(Socket, 0, Timeout),
- ok;
-%% Peer closed socket
-close({shutdown, transport_closed}, Socket, Transport = gen_tcp, ConnectionStates, Check) ->
- close({close, 0}, Socket, Transport, ConnectionStates, Check);
-%% We generate fatal alert
-close({shutdown, own_alert}, Socket, Transport = gen_tcp, ConnectionStates, Check) ->
- %% Standard trick to try to make sure all
- %% data sent to the tcp port is really delivered to the
- %% peer application before tcp port is closed so that the peer will
- %% get the correct TLS alert message and not only a transport close.
- %% Will return when other side has closed or after timout millisec
- %% e.g. we do not want to hang if something goes wrong
- %% with the network but we want to maximise the odds that
- %% peer application gets all data sent on the tcp connection.
- close({close, ?DEFAULT_TIMEOUT}, Socket, Transport, ConnectionStates, Check);
-close(downgrade, _,_,_,_) ->
- ok;
-%% Other
-close(_, Socket, Transport, _,_) ->
- tls_socket:close(Transport, Socket).
-protocol_name() ->
- "TLS".
-
-%%====================================================================
-%% Data handling
-%%====================================================================
-
-socket(Pids, Transport, Socket, Trackers) ->
- tls_socket:socket(Pids, Transport, Socket, ?MODULE, Trackers).
-
-setopts(Transport, Socket, Other) ->
- tls_socket:setopts(Transport, Socket, Other).
-
-getopts(Transport, Socket, Tag) ->
- tls_socket:getopts(Transport, Socket, Tag).
+ tls_gen_connection:next_event(hello, no_record, State, Actions).
%%--------------------------------------------------------------------
%% State functions
%%--------------------------------------------------------------------
%%--------------------------------------------------------------------
--spec init(gen_statem:event_type(),
- {start, timeout()} | term(), #state{}) ->
- gen_statem:state_function_result().
+-spec initial_hello(gen_statem:event_type(),
+ {start, timeout()} | term(), #state{}) ->
+ gen_statem:state_function_result().
%%--------------------------------------------------------------------
-
-init({call, From}, {start, Timeout},
- #state{static_env = #static_env{role = client,
- host = Host,
- port = Port,
- transport_cb = Transport,
- socket = Socket,
- session_cache = Cache,
- session_cache_cb = CacheCb},
- handshake_env = #handshake_env{renegotiation = {Renegotiation, _},
- ocsp_stapling_state = OcspState0} = HsEnv,
- connection_env = CEnv,
- ssl_options = #{log_level := LogLevel,
- %% Use highest version in initial ClientHello.
- %% Versions is a descending list of supported versions.
- versions := [HelloVersion|_] = Versions,
- session_tickets := SessionTickets,
- ocsp_stapling := OcspStaplingOpt,
- ocsp_nonce := OcspNonceOpt} = SslOpts,
- session = NewSession,
- connection_states = ConnectionStates0
- } = State0) ->
- KeyShare = maybe_generate_client_shares(SslOpts),
- Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, NewSession),
- %% Update UseTicket in case of automatic session resumption
- {UseTicket, State1} = tls_handshake_1_3:maybe_automatic_session_resumption(State0),
- TicketData = tls_handshake_1_3:get_ticket_data(self(), SessionTickets, UseTicket),
- OcspNonce = tls_handshake:ocsp_nonce(OcspNonceOpt, OcspStaplingOpt),
- Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
- Session#session.session_id,
- Renegotiation,
- Session#session.own_certificate,
- KeyShare,
- TicketData,
- OcspNonce),
-
- Handshake0 = ssl_handshake:init_handshake_history(),
-
- %% Update pre_shared_key extension with binders (TLS 1.3)
- Hello1 = tls_handshake_1_3:maybe_add_binders(Hello, TicketData, HelloVersion),
-
- MaxFragEnum = maps:get(max_frag_enum, Hello1#client_hello.extensions, undefined),
- ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0),
-
- {BinMsg, ConnectionStates, Handshake} =
- encode_handshake(Hello1, HelloVersion, ConnectionStates1, Handshake0),
-
- tls_socket:send(Transport, Socket, BinMsg),
- ssl_logger:debug(LogLevel, outbound, 'handshake', Hello1),
- ssl_logger:debug(LogLevel, outbound, 'record', BinMsg),
-
- %% RequestedVersion is used as the legacy record protocol version and shall be
- %% {3,3} in case of TLS 1.2 and higher. In all other cases it defaults to the
- %% lowest supported protocol version.
- %%
- %% negotiated_version is also used by the TLS 1.3 state machine and is set after
- %% ServerHello is processed.
- RequestedVersion = tls_record:hello_version(Versions),
- State = State1#state{connection_states = ConnectionStates,
- connection_env = CEnv#connection_env{
- negotiated_version = RequestedVersion},
- session = Session,
- handshake_env = HsEnv#handshake_env{
- tls_handshake_history = Handshake,
- ocsp_stapling_state = OcspState0#{ocsp_nonce => OcspNonce}},
- start_or_recv_from = From,
- key_share = KeyShare},
- next_event(hello, no_record, State, [{{timeout, handshake}, Timeout, close}]);
-
-init(Type, Event, State) ->
- gen_handshake(?FUNCTION_NAME, Type, Event, State).
+initial_hello(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
%%--------------------------------------------------------------------
--spec error(gen_statem:event_type(),
+-spec config_error(gen_statem:event_type(),
{start, timeout()} | term(), #state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
-error({call, From}, {start, _Timeout},
- #state{protocol_specific = #{error := Error}} = State) ->
- {stop_and_reply, {shutdown, normal},
- [{reply, From, {error, Error}}], State};
-
-error({call, _} = Call, Msg, State) ->
- gen_handshake(?FUNCTION_NAME, Call, Msg, State);
-error(_, _, _) ->
- {keep_state_and_data, [postpone]}.
+config_error(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
%%--------------------------------------------------------------------
-spec hello(gen_statem:event_type(),
@@ -699,52 +221,22 @@ hello(internal, #server_hello{extensions = Extensions} = Hello,
{next_state, user_hello,
State#state{start_or_recv_from = undefined,
handshake_env = HsEnv#handshake_env{
- hello = Hello}}, [{reply, From, {ok, Extensions}}]};
-hello(internal, #client_hello{client_version = ClientVersion} = Hello,
- #state{connection_states = ConnectionStates0,
- static_env = #static_env{
- trackers = Trackers},
- handshake_env = #handshake_env{kex_algorithm = KeyExAlg,
- renegotiation = {Renegotiation, _},
- negotiated_protocol = CurrentProtocol} = HsEnv,
- connection_env = CEnv,
- session = #session{own_certificate = Cert} = Session0,
- ssl_options = SslOpts} = State) ->
-
- case choose_tls_version(SslOpts, Hello) of
- 'tls_v1.3' ->
+ hello = Hello}}, [{reply, From, {ok, Extensions}}]};
+hello(internal, #client_hello{client_version = ClientVersion} = Hello, #state{ssl_options = SslOpts0,
+ connection_env = CEnv} = State0) ->
+ case choose_tls_fsm(SslOpts0, Hello) of
+ tls_1_3_fsm ->
%% Continue in TLS 1.3 'start' state
- {next_state, start, State, [{next_event, internal, Hello}]};
- 'tls_v1.2' ->
- SessionTracker = proplists:get_value(session_id_tracker, Trackers),
- case tls_handshake:hello(Hello,
- SslOpts,
- {SessionTracker, Session0,
- ConnectionStates0, Cert, KeyExAlg},
- Renegotiation) of
- #alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, ClientVersion, hello,
- State#state{connection_env = CEnv#connection_env{negotiated_version
- = ClientVersion}});
- {Version, {Type, Session},
- ConnectionStates, Protocol0, ServerHelloExt, HashSign} ->
- Protocol = case Protocol0 of
- undefined -> CurrentProtocol;
- _ -> Protocol0
- end,
- gen_handshake(?FUNCTION_NAME,
- internal,
- {common_client_hello, Type, ServerHelloExt},
- State#state{connection_states = ConnectionStates,
- connection_env = CEnv#connection_env{negotiated_version = Version},
- handshake_env = HsEnv#handshake_env{
- hashsign_algorithm = HashSign,
- client_hello_version = ClientVersion,
- negotiated_protocol = Protocol},
- session = Session
- })
+ {next_state, start, State0, [{change_callback_module, tls_connection_1_3}, {next_event, internal, Hello}]};
+ tls_1_0_to_1_2_fsm ->
+ case handle_client_hello(Hello, State0) of
+ {ServerHelloExt, Type, State} ->
+ {next_state, hello, State, [{next_event, internal, {common_client_hello, Type, ServerHelloExt}}]};
+ Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, ClientVersion, hello,
+ State0#state{connection_env = CEnv#connection_env{negotiated_version
+ = ClientVersion}})
end
-
end;
hello(internal, #server_hello{} = Hello,
#state{connection_states = ConnectionStates0,
@@ -757,13 +249,13 @@ hello(internal, #server_hello{} = Hello,
ssl_options = SslOptions} = State) ->
case tls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation, OldId) of
#alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, ReqVersion, hello,
+ ssl_gen_statem:handle_own_alert(Alert, ReqVersion, hello,
State#state{connection_env =
CEnv#connection_env{negotiated_version = ReqVersion}
});
%% Legacy TLS 1.2 and older
{Version, NewId, ConnectionStates, ProtoExt, Protocol, OcspState} ->
- ssl_connection:handle_session(Hello,
+ tls_dtls_connection:handle_session(Hello,
Version, NewId, ConnectionStates, ProtoExt, Protocol,
State#state{
handshake_env = HsEnv#handshake_env{
@@ -774,15 +266,15 @@ hello(internal, #server_hello{} = Hello,
{next_state, wait_sh,
State#state{handshake_env = HsEnv#handshake_env{ocsp_stapling_state = maps:merge(OcspState0,OcspState)},
connection_env = CEnv#connection_env{negotiated_version = SelectedVersion}},
- [{next_event, internal, Hello}]}
+ [{change_callback_module, tls_connection_1_3}, {next_event, internal, Hello}]}
end;
hello(info, Event, State) ->
- handle_info(Event, ?FUNCTION_NAME, State);
+ tls_gen_connection:handle_info(Event, ?FUNCTION_NAME, State);
hello(Type, Event, State) ->
- gen_handshake(?FUNCTION_NAME, Type, Event, State).
+ tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State).
user_hello(Type, Event, State) ->
- gen_handshake(?FUNCTION_NAME, Type, Event, State).
+ tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State).
%%--------------------------------------------------------------------
-spec abbreviated(gen_statem:event_type(), term(), #state{}) ->
@@ -791,7 +283,7 @@ user_hello(Type, Event, State) ->
abbreviated(info, Event, State) ->
gen_info(Event, ?FUNCTION_NAME, State);
abbreviated(Type, Event, State) ->
- gen_handshake(?FUNCTION_NAME, Type, Event, State).
+ tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State).
%%--------------------------------------------------------------------
-spec wait_ocsp_stapling(gen_statem:event_type(), term(), #state{}) ->
@@ -800,7 +292,7 @@ abbreviated(Type, Event, State) ->
wait_ocsp_stapling(info, Event, State) ->
gen_info(Event, ?FUNCTION_NAME, State);
wait_ocsp_stapling(Type, Event, State) ->
- gen_handshake(?FUNCTION_NAME, Type, Event, State).
+ tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State).
%%--------------------------------------------------------------------
-spec certify(gen_statem:event_type(), term(), #state{}) ->
@@ -809,7 +301,7 @@ wait_ocsp_stapling(Type, Event, State) ->
certify(info, Event, State) ->
gen_info(Event, ?FUNCTION_NAME, State);
certify(Type, Event, State) ->
- gen_handshake(?FUNCTION_NAME, Type, Event, State).
+ tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State).
%%--------------------------------------------------------------------
-spec cipher(gen_statem:event_type(), term(), #state{}) ->
@@ -818,7 +310,7 @@ certify(Type, Event, State) ->
cipher(info, Event, State) ->
gen_info(Event, ?FUNCTION_NAME, State);
cipher(Type, Event, State) ->
- gen_handshake(?FUNCTION_NAME, Type, Event, State).
+ tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State).
%%--------------------------------------------------------------------
-spec connection(gen_statem:event_type(),
@@ -831,42 +323,6 @@ connection({call, From}, {user_renegotiate, WriteState},
#state{connection_states = ConnectionStates} = State) ->
{next_state, ?FUNCTION_NAME, State#state{connection_states = ConnectionStates#{current_write => WriteState}},
[{next_event,{call, From}, renegotiate}]};
-connection({call, From},
- {close, {Pid, _Timeout}},
- #state{connection_env = #connection_env{terminated = closed} = CEnv,
- protocol_specific = PS} = State) ->
- {next_state, downgrade, State#state{connection_env =
- CEnv#connection_env{terminated = true,
- downgrade = {Pid, From}},
- protocol_specific = PS#{active_n_toggle => true,
- active_n => 1}
- },
- [{next_event, internal, ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY)}]};
-connection({call, From},
- {close,{Pid, Timeout}},
- #state{connection_states = ConnectionStates,
- protocol_specific = #{sender := Sender} = PS,
- connection_env = CEnv
- } = State0) ->
- case tls_sender:downgrade(Sender, Timeout) of
- {ok, Write} ->
- %% User downgrades connection
- %% When downgrading an TLS connection to a transport connection
- %% we must recive the close alert from the peer before releasing the
- %% transport socket.
- State = send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
- State0#state{connection_states =
- ConnectionStates#{current_write => Write}}),
- {next_state, downgrade, State#state{connection_env =
- CEnv#connection_env{downgrade = {Pid, From},
- terminated = true},
- protocol_specific = PS#{active_n_toggle => true,
- active_n => 1}
- },
- [{timeout, Timeout, downgrade}]};
- {error, timeout} ->
- {stop_and_reply, {shutdown, downgrade_fail}, [{reply, From, {error, timeout}}]}
- end;
connection(internal, #hello_request{},
#state{static_env = #static_env{role = client,
host = Host,
@@ -876,7 +332,7 @@ connection(internal, #hello_request{},
handshake_env = #handshake_env{
renegotiation = {Renegotiation, peer},
ocsp_stapling_state = OcspState},
- session = #session{own_certificate = Cert} = Session0,
+ session = #session{own_certificates = OwnCerts} = Session0,
ssl_options = SslOpts,
protocol_specific = #{sender := Pid},
connection_states = ConnectionStates} = State0) ->
@@ -885,11 +341,13 @@ connection(internal, #hello_request{},
Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, Session0),
Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts,
Session#session.session_id,
- Renegotiation, Cert, undefined,
+ Renegotiation, OwnCerts, undefined,
undefined, maps:get(ocsp_nonce, OcspState, undefined)),
- {State, Actions} = send_handshake(Hello, State0#state{connection_states = ConnectionStates#{current_write => Write},
- session = Session}),
- next_event(hello, no_record, State, Actions)
+ {State, Actions} = tls_gen_connection:send_handshake(Hello,
+ State0#state{connection_states =
+ ConnectionStates#{current_write => Write},
+ session = Session}),
+ tls_gen_connection:next_event(hello, no_record, State, Actions)
catch
_:_ ->
{stop, {shutdown, sender_blocked}, State0}
@@ -901,15 +359,15 @@ connection(internal, #hello_request{},
handshake_env = #handshake_env{
renegotiation = {Renegotiation, _},
ocsp_stapling_state = OcspState},
- session = #session{own_certificate = Cert},
+ session = #session{own_certificates = OwnCerts},
ssl_options = SslOpts,
connection_states = ConnectionStates} = State0) ->
Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts,
- <<>>, Renegotiation, Cert, undefined,
+ <<>>, Renegotiation, OwnCerts, undefined,
undefined, maps:get(ocsp_nonce, OcspState, undefined)),
- {State, Actions} = send_handshake(Hello, State0),
- next_event(hello, no_record, State, Actions);
+ {State, Actions} = tls_gen_connection:send_handshake(Hello, State0),
+ tls_gen_connection:next_event(hello, no_record, State, Actions);
connection(internal, #client_hello{} = Hello,
#state{static_env = #static_env{role = server},
handshake_env = #handshake_env{allow_renegotiate = true}= HsEnv,
@@ -923,36 +381,21 @@ connection(internal, #client_hello{} = Hello,
%% renegotiations immediately after each other.
erlang:send_after(?WAIT_TO_ALLOW_RENEGOTIATION, self(), allow_renegotiate),
{ok, Write} = tls_sender:renegotiate(Sender),
- next_event(hello, no_record, State#state{connection_states = CS#{current_write => Write},
- handshake_env = HsEnv#handshake_env{renegotiation = {true, peer},
- allow_renegotiate = false}
- },
- [{next_event, internal, Hello}]);
+ tls_gen_connection:next_event(hello, no_record,
+ State#state{connection_states = CS#{current_write => Write},
+ handshake_env = HsEnv#handshake_env{renegotiation = {true, peer},
+ allow_renegotiate = false}
+ },
+ [{next_event, internal, Hello}]);
connection(internal, #client_hello{},
#state{static_env = #static_env{role = server},
handshake_env = #handshake_env{allow_renegotiate = false}} = State0) ->
Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION),
- send_alert_in_connection(Alert, State0),
- State = reinit_handshake_data(State0),
- next_event(?FUNCTION_NAME, no_record, State);
-
-connection(internal, #new_session_ticket{} = NewSessionTicket, State) ->
- %% TLS 1.3
- handle_new_session_ticket(NewSessionTicket, State),
- next_event(?FUNCTION_NAME, no_record, State);
-
-connection(internal, #key_update{} = KeyUpdate, State0) ->
- %% TLS 1.3
- case handle_key_update(KeyUpdate, State0) of
- {ok, State} ->
- next_event(?FUNCTION_NAME, no_record, State);
- {error, State, Alert} ->
- ssl_connection:handle_own_alert(Alert, {3,4}, connection, State),
- next_event(?FUNCTION_NAME, no_record, State)
- end;
-
+ tls_gen_connection:send_alert_in_connection(Alert, State0),
+ State = tls_gen_connection:reinit_handshake_data(State0),
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
connection(Type, Event, State) ->
- ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE).
+ tls_dtls_connection:?FUNCTION_NAME(Type, Event, State).
%%--------------------------------------------------------------------
-spec downgrade(gen_statem:event_type(), term(), #state{}) ->
@@ -974,120 +417,9 @@ downgrade(info, {CloseTag, Socket},
State) ->
{stop_and_reply, {shutdown, normal},[{reply, From, {error, CloseTag}}], State};
downgrade(info, Info, State) ->
- handle_info(Info, ?FUNCTION_NAME, State);
+ tls_gen_connection:handle_info(Info, ?FUNCTION_NAME, State);
downgrade(Type, Event, State) ->
- ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE).
-
-%%--------------------------------------------------------------------
-%% TLS 1.3 state functions
-%%--------------------------------------------------------------------
-%%--------------------------------------------------------------------
--spec start(gen_statem:event_type(), term(), #state{}) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-start(info, Event, State) ->
- gen_info_1_3(Event, ?FUNCTION_NAME, State);
-start(Type, Event, State) ->
- gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
-
-%%--------------------------------------------------------------------
--spec negotiated(gen_statem:event_type(), term(), #state{}) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-negotiated(info, Event, State) ->
- gen_info_1_3(Event, ?FUNCTION_NAME, State);
-negotiated(Type, Event, State) ->
- gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
-
-%%--------------------------------------------------------------------
--spec recvd_ch(gen_statem:event_type(), term(), #state{}) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-recvd_ch(info, Event, State) ->
- gen_info_1_3(Event, ?FUNCTION_NAME, State);
-recvd_ch(Type, Event, State) ->
- gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
-
-%%--------------------------------------------------------------------
--spec wait_cert(gen_statem:event_type(), term(), #state{}) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-wait_cert(info, Event, State) ->
- gen_info_1_3(Event, ?FUNCTION_NAME, State);
-wait_cert(Type, Event, State) ->
- gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
-
-%%--------------------------------------------------------------------
--spec wait_cv(gen_statem:event_type(), term(), #state{}) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-wait_cv(info, Event, State) ->
- gen_info_1_3(Event, ?FUNCTION_NAME, State);
-wait_cv(Type, Event, State) ->
- gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
-
-%%--------------------------------------------------------------------
--spec wait_eoed(gen_statem:event_type(), term(), #state{}) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-wait_eoed(info, Event, State) ->
- gen_info_1_3(Event, ?FUNCTION_NAME, State);
-wait_eoed(Type, Event, State) ->
- gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
-
-%%--------------------------------------------------------------------
--spec wait_finished(gen_statem:event_type(), term(), #state{}) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-wait_finished(info, Event, State) ->
- gen_info_1_3(Event, ?FUNCTION_NAME, State);
-wait_finished(Type, Event, State) ->
- gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
-
-%%--------------------------------------------------------------------
--spec wait_flight2(gen_statem:event_type(), term(), #state{}) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-wait_flight2(info, Event, State) ->
- gen_info_1_3(Event, ?FUNCTION_NAME, State);
-wait_flight2(Type, Event, State) ->
- gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
-
-%%--------------------------------------------------------------------
--spec connected(gen_statem:event_type(), term(), #state{}) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-connected(info, Event, State) ->
- gen_info_1_3(Event, ?FUNCTION_NAME, State);
-connected(Type, Event, State) ->
- gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
-
-%%--------------------------------------------------------------------
--spec wait_cert_cr(gen_statem:event_type(), term(), #state{}) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-wait_cert_cr(info, Event, State) ->
- gen_info_1_3(Event, ?FUNCTION_NAME, State);
-wait_cert_cr(Type, Event, State) ->
- gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
-
-%%--------------------------------------------------------------------
--spec wait_ee(gen_statem:event_type(), term(), #state{}) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-wait_ee(info, Event, State) ->
- gen_info_1_3(Event, ?FUNCTION_NAME, State);
-wait_ee(Type, Event, State) ->
- gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
-
-%%--------------------------------------------------------------------
--spec wait_sh(gen_statem:event_type(), term(), #state{}) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-wait_sh(info, Event, State) ->
- gen_info_1_3(Event, ?FUNCTION_NAME, State);
-wait_sh(Type, Event, State) ->
- gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
+ tls_dtls_connection:?FUNCTION_NAME(Type, Event, State).
%--------------------------------------------------------------------
%% gen_statem callbacks
@@ -1099,14 +431,14 @@ terminate({shutdown, {sender_died, Reason}}, _StateName,
#state{static_env = #static_env{socket = Socket,
transport_cb = Transport}}
= State) ->
- ssl_connection:handle_trusted_certs_db(State),
- close(Reason, Socket, Transport, undefined, undefined);
+ ssl_gen_statem:handle_trusted_certs_db(State),
+ tls_gen_connection:close(Reason, Socket, Transport, undefined, undefined);
terminate(Reason, StateName, State) ->
- catch ssl_connection:terminate(Reason, StateName, State),
+ catch ssl_gen_statem:terminate(Reason, StateName, State),
ensure_sender_terminate(Reason, State).
format_status(Type, Data) ->
- ssl_connection:format_status(Type, Data).
+ ssl_gen_statem:format_status(Type, Data).
code_change(_OldVsn, StateName, State, _) ->
{ok, StateName, State}.
@@ -1136,7 +468,7 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac
InitStatEnv = #static_env{
role = Role,
transport_cb = CbModule,
- protocol_cb = ?MODULE,
+ protocol_cb = tls_gen_connection,
data_tag = DataTag,
close_tag = CloseTag,
error_tag = ErrorTag,
@@ -1169,288 +501,75 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac
}
}.
-initialize_tls_sender(#state{static_env = #static_env{
- role = Role,
- transport_cb = Transport,
- socket = Socket,
- trackers = Trackers
- },
- connection_env = #connection_env{negotiated_version = Version},
- socket_options = SockOpts,
- ssl_options = #{renegotiate_at := RenegotiateAt,
- key_update_at := KeyUpdateAt,
- log_level := LogLevel},
- connection_states = #{current_write := ConnectionWriteState},
- protocol_specific = #{sender := Sender}}) ->
- Init = #{current_write => ConnectionWriteState,
- role => Role,
- socket => Socket,
- socket_options => SockOpts,
- trackers => Trackers,
- transport_cb => Transport,
- negotiated_version => Version,
- renegotiate_at => RenegotiateAt,
- key_update_at => KeyUpdateAt,
- log_level => LogLevel},
- tls_sender:initialize(Sender, Init).
-
-next_tls_record(Data, StateName,
- #state{protocol_buffers =
- #protocol_buffers{tls_record_buffer = Buf0,
- tls_cipher_texts = CT0} = Buffers,
- ssl_options = SslOpts} = State0) ->
- Versions =
- %% TLSPlaintext.legacy_record_version is ignored in TLS 1.3 and thus all
- %% record version are accepted when receiving initial ClientHello and
- %% ServerHello. This can happen in state 'hello' in case of all TLS
- %% versions and also in state 'start' when TLS 1.3 is negotiated.
- %% After the version is negotiated all subsequent TLS records shall have
- %% the proper legacy_record_version (= negotiated_version).
- %% Note: TLS record version {3,4} is used internally in TLS 1.3 and at this
- %% point it is the same as the negotiated protocol version.
- %% TODO: Refactor state machine and introduce a record_protocol_version beside
- %% the negotiated_version.
- case StateName of
- State when State =:= hello orelse
- State =:= start ->
- [tls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_VERSIONS];
- _ ->
- State0#state.connection_env#connection_env.negotiated_version
- end,
- #{current_write := #{max_fragment_length := MaxFragLen}} = State0#state.connection_states,
- case tls_record:get_tls_records(Data, Versions, Buf0, MaxFragLen, SslOpts) of
- {Records, Buf1} ->
- CT1 = CT0 ++ Records,
- next_record(StateName, State0#state{protocol_buffers =
- Buffers#protocol_buffers{tls_record_buffer = Buf1,
- tls_cipher_texts = CT1}});
- #alert{} = Alert ->
- handle_record_alert(Alert, State0)
- end.
-
-
-handle_record_alert(Alert, _) ->
- Alert.
-
-tls_handshake_events(Packets) ->
- lists:map(fun(Packet) ->
- {next_event, internal, {handshake, Packet}}
- end, Packets).
-
-%% raw data from socket, upack records
-handle_info({Protocol, _, Data}, StateName,
- #state{static_env = #static_env{data_tag = Protocol},
- connection_env = #connection_env{negotiated_version = Version}} = State0) ->
- case next_tls_record(Data, StateName, State0) of
- {Record, State} ->
- next_event(StateName, Record, State);
- #alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, Version, StateName, State0)
- end;
-handle_info({PassiveTag, Socket}, StateName,
- #state{static_env = #static_env{socket = Socket,
- passive_tag = PassiveTag},
- start_or_recv_from = From,
- protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs},
- protocol_specific = PS
- } = State0) ->
- case (From =/= undefined) andalso (CTs == []) of
- true ->
- {Record, State} = activate_socket(State0#state{protocol_specific = PS#{active_n_toggle => true}}),
- next_event(StateName, Record, State);
- false ->
- next_event(StateName, no_record,
- State0#state{protocol_specific = PS#{active_n_toggle => true}})
- end;
-handle_info({CloseTag, Socket}, StateName,
- #state{static_env = #static_env{
- role = Role,
- host = Host,
- port = Port,
- socket = Socket,
- close_tag = CloseTag},
- handshake_env = #handshake_env{renegotiation = Type},
- connection_env = #connection_env{negotiated_version = Version},
- session = Session} = State) when StateName =/= connection ->
- ssl_connection:maybe_invalidate_session(Version, Type, Role, Host, Port, Session),
- Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, transport_closed),
- ssl_connection:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State),
- {stop, {shutdown, transport_closed}, State};
-handle_info({CloseTag, Socket}, StateName,
- #state{static_env = #static_env{
- role = Role,
- socket = Socket,
- close_tag = CloseTag},
- socket_options = #socket_options{active = Active},
- protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs},
- user_data_buffer = {_,BufferSize,_},
- protocol_specific = PS} = State) ->
-
- %% Note that as of TLS 1.1,
- %% failure to properly close a connection no longer requires that a
- %% session not be resumed. This is a change from TLS 1.0 to conform
- %% with widespread implementation practice.
-
- case (Active == false) andalso ((CTs =/= []) or (BufferSize =/= 0)) of
- false ->
- %% As invalidate_sessions here causes performance issues,
- %% we will conform to the widespread implementation
- %% practice and go aginst the spec
- %% case Version of
- %% {3, N} when N >= 1 ->
- %% ok;
- %% _ ->
- %% invalidate_session(Role, Host, Port, Session)
- %% ok
- %% end,
- Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, transport_closed),
- ssl_connection:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State),
- {stop, {shutdown, transport_closed}, State};
- true ->
- %% Fixes non-delivery of final TLS record in {active, once}.
- %% Basically allows the application the opportunity to set {active, once} again
- %% and then receive the final message. Set internal active_n to zero
- %% to ensure socket close message is sent if there is not enough data to deliver.
- next_event(StateName, no_record, State#state{protocol_specific = PS#{active_n_toggle => true}})
- end;
-handle_info({'EXIT', Sender, Reason}, _,
- #state{protocol_specific = #{sender := Sender}} = State) ->
- {stop, {shutdown, {sender_died, Reason}}, State};
-handle_info(Msg, StateName, State) ->
- ssl_connection:StateName(info, Msg, State, ?MODULE).
-
-handle_alerts([], Result) ->
- Result;
-handle_alerts(_, {stop, _, _} = Stop) ->
- Stop;
-handle_alerts([#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} | _Alerts],
- {next_state, connection = StateName, #state{connection_env = CEnv,
- socket_options = #socket_options{active = false},
- user_data_buffer = {_,BufferSize,_},
- protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs}} =
- State}) when (BufferSize =/= 0) orelse
- (CTs =/= []) ->
- {next_state, StateName, State#state{connection_env = CEnv#connection_env{terminated = true}}};
-handle_alerts([Alert | Alerts], {next_state, StateName, State}) ->
- handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State));
-handle_alerts([Alert | Alerts], {next_state, StateName, State, _Actions}) ->
- handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State)).
-
-encode_handshake(Handshake, Version, ConnectionStates0, Hist0) ->
- Frag = tls_handshake:encode_handshake(Handshake, Version),
- Hist = ssl_handshake:update_handshake_history(Hist0, Frag),
- {Encoded, ConnectionStates} =
- tls_record:encode_handshake(Frag, Version, ConnectionStates0),
- {Encoded, ConnectionStates, Hist}.
-
-encode_change_cipher(#change_cipher_spec{}, Version, ConnectionStates) ->
- tls_record:encode_change_cipher_spec(Version, ConnectionStates).
-
-decode_alerts(Bin) ->
- ssl_alert:decode(Bin).
-
-gen_handshake(StateName, Type, Event,
- #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
- try ssl_connection:StateName(Type, Event, State, ?MODULE) of
- Result ->
- Result
- catch
- _:_ ->
- ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
- malformed_handshake_data),
- Version, StateName, State)
- end.
-
-
-gen_handshake_1_3(StateName, Type, Event,
- #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
- try tls_connection_1_3:StateName(Type, Event, State, ?MODULE) of
- Result ->
- Result
- catch
- _:_ ->
- ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
- malformed_handshake_data),
- Version, StateName, State)
+handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, State0) ->
+ case tls_dtls_connection:handle_sni_extension(State0, Hello) of
+ #state{connection_states = ConnectionStates0,
+ static_env = #static_env{trackers = Trackers},
+ handshake_env = #handshake_env{
+ kex_algorithm = KeyExAlg,
+ renegotiation = {Renegotiation, _},
+ negotiated_protocol = CurrentProtocol,
+ sni_guided_cert_selection = SNICertSelection} = HsEnv,
+ connection_env = CEnv,
+ session = #session{own_certificates = OwnCerts} = Session0,
+ ssl_options = SslOpts} = State ->
+ SessionTracker = proplists:get_value(session_id_tracker, Trackers),
+ case tls_handshake:hello(Hello,
+ SslOpts,
+ {SessionTracker, Session0,
+ ConnectionStates0, OwnCerts, KeyExAlg},
+ Renegotiation) of
+ #alert{} = Alert ->
+ Alert;
+ {Version, {Type, Session},
+ ConnectionStates, Protocol0, ServerHelloExt0, HashSign} ->
+ Protocol = case Protocol0 of
+ undefined -> CurrentProtocol;
+ _ -> Protocol0
+ end,
+ ServerHelloExt =
+ case SNICertSelection of
+ true ->
+ ServerHelloExt0#{sni => #sni{hostname = ""}};
+ false ->
+ ServerHelloExt0
+ end,
+ {ServerHelloExt, Type, State#state{connection_states = ConnectionStates,
+ connection_env = CEnv#connection_env{negotiated_version = Version},
+ handshake_env = HsEnv#handshake_env{
+ hashsign_algorithm = HashSign,
+ client_hello_version = ClientVersion,
+ negotiated_protocol = Protocol},
+ session = Session
+ }}
+ end;
+ #alert{} = Alert ->
+ Alert
end.
gen_info(Event, connection = StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
- try handle_info(Event, StateName, State) of
+ try tls_gen_connection:handle_info(Event, StateName, State) of
Result ->
Result
catch
_:_ ->
- ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR,
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR,
malformed_data),
Version, StateName, State)
end;
gen_info(Event, StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
- try handle_info(Event, StateName, State) of
+ try tls_gen_connection:handle_info(Event, StateName, State) of
Result ->
Result
catch
_:_ ->
- ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
malformed_handshake_data),
Version, StateName, State)
end.
-
-gen_info_1_3(Event, connected = StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
- try handle_info(Event, StateName, State) of
- Result ->
- Result
- catch
- _:_ ->
- ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR,
- malformed_data),
- Version, StateName, State)
- end;
-
-gen_info_1_3(Event, StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
- try handle_info(Event, StateName, State) of
- Result ->
- Result
- catch
- _:_ ->
- ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
- malformed_handshake_data),
- Version, StateName, State)
- end.
-
-unprocessed_events(Events) ->
- %% The first handshake event will be processed immediately
- %% as it is entered first in the event queue and
- %% when it is processed there will be length(Events)-1
- %% handshake events left to process before we should
- %% process more TLS-records received on the socket.
- erlang:length(Events)-1.
-
-
-assert_buffer_sanity(<<?BYTE(_Type), ?UINT24(Length), Rest/binary>>,
- #{max_handshake_size := Max}) when
- Length =< Max ->
- case size(Rest) of
- N when N < Length ->
- true;
- N when N > Length ->
- throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
- too_big_handshake_data));
- _ ->
- throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
- malformed_handshake_data))
- end;
-assert_buffer_sanity(Bin, _) ->
- case size(Bin) of
- N when N < 3 ->
- true;
- _ ->
- throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
- malformed_handshake_data))
- end.
-
ensure_sender_terminate(downgrade, _) ->
ok; %% Do not terminate sender during downgrade phase
ensure_sender_terminate(_, #state{protocol_specific = #{sender := Sender}}) ->
@@ -1464,125 +583,17 @@ ensure_sender_terminate(_, #state{protocol_specific = #{sender := Sender}}) ->
end,
spawn(Kill).
-maybe_generate_client_shares(#{versions := [Version|_],
- supported_groups :=
- #supported_groups{
- supported_groups = [Group|_]}})
- when Version =:= {3,4} ->
- %% Generate only key_share entry for the most preferred group
- ssl_cipher:generate_client_shares([Group]);
-maybe_generate_client_shares(_) ->
- undefined.
-
-choose_tls_version(#{versions := Versions},
- #client_hello{
- extensions = #{client_hello_versions :=
- #client_hello_versions{versions = ClientVersions}
- }
- }) ->
+choose_tls_fsm(#{versions := Versions},
+ #client_hello{
+ extensions = #{client_hello_versions :=
+ #client_hello_versions{versions = ClientVersions}
+ }
+ }) ->
case ssl_handshake:select_supported_version(ClientVersions, Versions) of
{3,4} ->
- 'tls_v1.3';
+ tls_1_3_fsm;
_Else ->
- 'tls_v1.2'
+ tls_1_0_to_1_2_fsm
end;
-choose_tls_version(_, _) ->
- 'tls_v1.2'.
-
-
-%% Special version handling for TLS 1.3 clients:
-%% In the shared state 'init' negotiated_version is set to requested version and
-%% that is expected by the legacy part of the state machine. However, in order to
-%% be able to process new TLS 1.3 extensions, the effective version shall be set
-%% {3,4}.
-%% When highest supported version is {3,4} the negotiated version is set to {3,3}.
-effective_version({3,3} , #{versions := [Version|_]}, client) when Version >= {3,4} ->
- Version;
-%% Use highest supported version during startup (TLS server, all versions).
-effective_version(undefined, #{versions := [Version|_]}, _) ->
- Version;
-%% Use negotiated version in all other cases.
-effective_version(Version, _, _) ->
- Version.
-
-
-handle_new_session_ticket(_, #state{ssl_options = #{session_tickets := disabled}}) ->
- ok;
-handle_new_session_ticket(#new_session_ticket{ticket_nonce = Nonce} = NewSessionTicket,
- #state{connection_states = ConnectionStates,
- ssl_options = #{session_tickets := SessionTickets,
- server_name_indication := SNI},
- connection_env = #connection_env{user_application = {_, User}}})
- when SessionTickets =:= manual ->
- #{security_parameters := SecParams} =
- ssl_record:current_connection_state(ConnectionStates, read),
- HKDF = SecParams#security_parameters.prf_algorithm,
- RMS = SecParams#security_parameters.resumption_master_secret,
- PSK = tls_v1:pre_shared_key(RMS, Nonce, HKDF),
- send_ticket_data(User, NewSessionTicket, HKDF, SNI, PSK);
-handle_new_session_ticket(#new_session_ticket{ticket_nonce = Nonce} = NewSessionTicket,
- #state{connection_states = ConnectionStates,
- ssl_options = #{session_tickets := SessionTickets,
- server_name_indication := SNI}})
- when SessionTickets =:= auto ->
- #{security_parameters := SecParams} =
- ssl_record:current_connection_state(ConnectionStates, read),
- HKDF = SecParams#security_parameters.prf_algorithm,
- RMS = SecParams#security_parameters.resumption_master_secret,
- PSK = tls_v1:pre_shared_key(RMS, Nonce, HKDF),
- tls_client_ticket_store:store_ticket(NewSessionTicket, HKDF, SNI, PSK).
-
-
-handle_key_update(#key_update{request_update = update_not_requested}, State0) ->
- %% Update read key in connection
- {ok, update_cipher_key(current_read, State0)};
-handle_key_update(#key_update{request_update = update_requested},
- #state{protocol_specific = #{sender := Sender}} = State0) ->
- %% Update read key in connection
- State1 = update_cipher_key(current_read, State0),
- %% Send key_update and update sender's write key
- case send_key_update(Sender, update_not_requested) of
- ok ->
- {ok, State1};
- {error, Reason} ->
- {error, State1, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason)}
- end.
-
-
-update_cipher_key(ConnStateName, #state{connection_states = CS0} = State0) ->
- CS = update_cipher_key(ConnStateName, CS0),
- State0#state{connection_states = CS};
-update_cipher_key(ConnStateName, CS0) ->
- #{security_parameters := SecParams0,
- cipher_state := CipherState0} = ConnState0 = maps:get(ConnStateName, CS0),
- HKDF = SecParams0#security_parameters.prf_algorithm,
- CipherSuite = SecParams0#security_parameters.cipher_suite,
- ApplicationTrafficSecret0 = SecParams0#security_parameters.application_traffic_secret,
- ApplicationTrafficSecret = tls_v1:update_traffic_secret(HKDF, ApplicationTrafficSecret0),
-
- %% Calculate traffic keys
- #{cipher := Cipher} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
- {Key, IV} = tls_v1:calculate_traffic_keys(HKDF, Cipher, ApplicationTrafficSecret),
-
- SecParams = SecParams0#security_parameters{application_traffic_secret = ApplicationTrafficSecret},
- CipherState = CipherState0#cipher_state{key = Key, iv = IV},
- ConnState = ConnState0#{security_parameters => SecParams,
- cipher_state => CipherState,
- sequence_number => 0},
- CS0#{ConnStateName => ConnState}.
-
-
-send_key_update(Sender, Type) ->
- KeyUpdate = tls_handshake_1_3:key_update(Type),
- tls_sender:send_post_handshake(Sender, KeyUpdate).
-
-
-%% Send ticket data to user as opaque binary
-send_ticket_data(User, NewSessionTicket, HKDF, SNI, PSK) ->
- Timestamp = erlang:system_time(seconds),
- TicketData = #{hkdf => HKDF,
- sni => SNI,
- psk => PSK,
- timestamp => Timestamp,
- ticket => NewSessionTicket},
- User ! {ssl, session_ticket, {SNI, erlang:term_to_binary(TicketData)}}.
+choose_tls_fsm(_, _) ->
+ tls_1_0_to_1_2_fsm.
diff --git a/lib/ssl/src/tls_connection_1_3.erl b/lib/ssl/src/tls_connection_1_3.erl
index 0b5ff98474..fc4b4f673f 100644
--- a/lib/ssl/src/tls_connection_1_3.erl
+++ b/lib/ssl/src/tls_connection_1_3.erl
@@ -20,9 +20,16 @@
%%
%%----------------------------------------------------------------------
-%% Purpose: TODO
+%% Purpose: TLS-1.3 FSM
%%----------------------------------------------------------------------
-
+%% INITIAL_HELLO
+%% Client send
+%% first ClientHello
+%% | ---> CONFIG_ERROR
+%% | Send error to user
+%% | and shutdown
+%% |
+%% V
%% RFC 8446
%% A.1. Client
%%
@@ -104,27 +111,162 @@
-include("ssl_alert.hrl").
-include("ssl_connection.hrl").
+-include("tls_connection.hrl").
-include("tls_handshake.hrl").
-include("tls_handshake_1_3.hrl").
-%% gen_statem helper functions
--export([start/4,
- negotiated/4,
- wait_cert/4,
- wait_cv/4,
- wait_finished/4,
- wait_sh/4,
- wait_ee/4,
- wait_cert_cr/4
+-behaviour(gen_statem).
+
+%% gen_statem callbacks
+-export([init/1, callback_mode/0, terminate/3, code_change/4, format_status/2]).
+
+%% gen_statem state functions
+-export([initial_hello/3,
+ config_error/3,
+ user_hello/3,
+ start/3,
+ negotiated/3,
+ wait_cert/3,
+ wait_cv/3,
+ wait_finished/3,
+ wait_sh/3,
+ wait_ee/3,
+ wait_cert_cr/3,
+ connection/3,
+ downgrade/3
]).
+%% Internal API
+-export([setopts/3,
+ getopts/3,
+ send_key_update/2,
+ update_cipher_key/2]).
+
+%%====================================================================
+%% Internal API
+%%====================================================================
+
+setopts(Transport, Socket, Other) ->
+ tls_socket:setopts(Transport, Socket, Other).
+
+getopts(Transport, Socket, Tag) ->
+ tls_socket:getopts(Transport, Socket, Tag).
+
+send_key_update(Sender, Type) ->
+ KeyUpdate = tls_handshake_1_3:key_update(Type),
+ tls_sender:send_post_handshake(Sender, KeyUpdate).
+
+update_cipher_key(ConnStateName, #state{connection_states = CS0} = State0) ->
+ CS = update_cipher_key(ConnStateName, CS0),
+ State0#state{connection_states = CS};
+update_cipher_key(ConnStateName, CS0) ->
+ #{security_parameters := SecParams0,
+ cipher_state := CipherState0} = ConnState0 = maps:get(ConnStateName, CS0),
+ HKDF = SecParams0#security_parameters.prf_algorithm,
+ CipherSuite = SecParams0#security_parameters.cipher_suite,
+ ApplicationTrafficSecret0 = SecParams0#security_parameters.application_traffic_secret,
+ ApplicationTrafficSecret = tls_v1:update_traffic_secret(HKDF, ApplicationTrafficSecret0),
+
+ %% Calculate traffic keys
+ #{cipher := Cipher} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
+ {Key, IV} = tls_v1:calculate_traffic_keys(HKDF, Cipher, ApplicationTrafficSecret),
+
+ SecParams = SecParams0#security_parameters{application_traffic_secret = ApplicationTrafficSecret},
+ CipherState = CipherState0#cipher_state{key = Key, iv = IV},
+ ConnState = ConnState0#{security_parameters => SecParams,
+ cipher_state => CipherState,
+ sequence_number => 0},
+ CS0#{ConnStateName => ConnState}.
+
+%--------------------------------------------------------------------
+%% gen_statem callbacks
+%%--------------------------------------------------------------------
+callback_mode() ->
+ state_functions.
+
+init([Role, Sender, Host, Port, Socket, Options, User, CbInfo]) ->
+ State0 = #state{protocol_specific = Map} = initial_state(Role, Sender,
+ Host, Port, Socket, Options, User, CbInfo),
+ try
+ State = ssl_gen_statem:ssl_config(State0#state.ssl_options, Role, State0),
+ tls_gen_connection:initialize_tls_sender(State),
+ gen_statem:enter_loop(?MODULE, [], initial_hello, State)
+ catch throw:Error ->
+ EState = State0#state{protocol_specific = Map#{error => Error}},
+ gen_statem:enter_loop(?MODULE, [], config_error, EState)
+ end.
+
+terminate({shutdown, {sender_died, Reason}}, _StateName,
+ #state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport}}
+ = State) ->
+ ssl_gen_statem:handle_trusted_certs_db(State),
+ tls_gen_connection:close(Reason, Socket, Transport, undefined, undefined);
+terminate(Reason, StateName, State) ->
+ ssl_gen_statem:terminate(Reason, StateName, State).
+
+format_status(Type, Data) ->
+ ssl_gen_statem:format_status(Type, Data).
+
+code_change(_OldVsn, StateName, State, _) ->
+ {ok, StateName, State}.
+
+%--------------------------------------------------------------------
+%% state callbacks
+%%--------------------------------------------------------------------
+%%--------------------------------------------------------------------
+-spec initial_hello(gen_statem:event_type(),
+ {start, timeout()} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+initial_hello(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec config_error(gen_statem:event_type(),
+ {start, timeout()} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+config_error(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
-start(internal, #change_cipher_spec{}, State, _Module) ->
- tls_connection:next_event(?FUNCTION_NAME, no_record, State);
-start(internal, #client_hello{} = Hello, State0, _Module) ->
+
+user_hello({call, From}, cancel, #state{connection_env = #connection_env{negotiated_version = Version}}
+ = State) ->
+ gen_statem:reply(From, ok),
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled),
+ Version, ?FUNCTION_NAME, State);
+user_hello({call, From}, {handshake_continue, NewOptions, Timeout},
+ #state{static_env = #static_env{role = Role},
+ handshake_env = #handshake_env{hello = Hello},
+ ssl_options = Options0} = State0) ->
+ Options = ssl:handle_options(NewOptions, Role, Options0#{handshake => full}),
+ State = ssl_gen_statem:ssl_config(Options, Role, State0),
+ Next = case Role of
+ client ->
+ wait_sh;
+ server ->
+ start
+ end,
+ {next_state, Next, State#state{start_or_recv_from = From},
+ [{next_event, internal, Hello}, {{timeout, handshake}, Timeout, close}]};
+user_hello(_, _, _) ->
+ {keep_state_and_data, [postpone]}.
+
+start(internal, #change_cipher_spec{}, State) ->
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
+start(internal, #client_hello{extensions = Extensions} = Hello,
+ #state{ssl_options = #{handshake := hello},
+ start_or_recv_from = From,
+ handshake_env = HsEnv} = State) ->
+ {next_state, user_hello,
+ State#state{start_or_recv_from = undefined,
+ handshake_env = HsEnv#handshake_env{
+ hello = Hello}}, [{reply, From, {ok, Extensions}}]};
+start(internal, #client_hello{} = Hello, State0) ->
case tls_handshake_1_3:do_start(Hello, State0) of
#alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, {3,4}, start, State0);
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, start, State0);
{State, start} ->
{next_state, start, State, []};
{State, negotiated} ->
@@ -132,116 +274,274 @@ start(internal, #client_hello{} = Hello, State0, _Module) ->
{State, negotiated, PSK} -> %% Session Resumption with PSK
{next_state, negotiated, State, [{next_event, internal, {start_handshake, PSK}}]}
end;
-start(internal, #server_hello{} = ServerHello, State0, _Module) ->
+start(internal, #server_hello{extensions = Extensions} = ServerHello,
+ #state{ssl_options = #{handshake := hello},
+ handshake_env = HsEnv,
+ start_or_recv_from = From}
+ = State) ->
+ {next_state, user_hello,
+ State#state{start_or_recv_from = undefined,
+ handshake_env = HsEnv#handshake_env{
+ hello = ServerHello}}, [{reply, From, {ok, Extensions}}]};
+start(internal, #server_hello{} = ServerHello, State0) ->
case tls_handshake_1_3:do_start(ServerHello, State0) of
#alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, {3,4}, start, State0);
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, start, State0);
{State, NextState} ->
{next_state, NextState, State, []}
end;
-start(Type, Msg, State, Connection) ->
- ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
-
+start(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+start(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-negotiated(internal, #change_cipher_spec{}, State, _Module) ->
- tls_connection:next_event(?FUNCTION_NAME, no_record, State);
-negotiated(internal, Message, State0, _Module) ->
+negotiated(internal, #change_cipher_spec{}, State) ->
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
+negotiated(internal, Message, State0) ->
case tls_handshake_1_3:do_negotiated(Message, State0) of
#alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, {3,4}, negotiated, State0);
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, negotiated, State0);
{State, NextState} ->
{next_state, NextState, State, []}
- end.
-
+ end;
+negotiated(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State).
-wait_cert(internal, #change_cipher_spec{}, State, _Module) ->
- tls_connection:next_event(?FUNCTION_NAME, no_record, State);
+wait_cert(internal, #change_cipher_spec{}, State) ->
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
wait_cert(internal,
- #certificate_1_3{} = Certificate, State0, _Module) ->
+ #certificate_1_3{} = Certificate, State0) ->
case tls_handshake_1_3:do_wait_cert(Certificate, State0) of
{#alert{} = Alert, State} ->
- ssl_connection:handle_own_alert(Alert, {3,4}, wait_cert, State);
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, wait_cert, State);
{State, NextState} ->
- tls_connection:next_event(NextState, no_record, State)
+ tls_gen_connection:next_event(NextState, no_record, State)
end;
-wait_cert(Type, Msg, State, Connection) ->
- ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
-
+wait_cert(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+wait_cert(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-wait_cv(internal, #change_cipher_spec{}, State, _Module) ->
- tls_connection:next_event(?FUNCTION_NAME, no_record, State);
+wait_cv(internal, #change_cipher_spec{}, State) ->
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
wait_cv(internal,
- #certificate_verify_1_3{} = CertificateVerify, State0, _Module) ->
+ #certificate_verify_1_3{} = CertificateVerify, State0) ->
case tls_handshake_1_3:do_wait_cv(CertificateVerify, State0) of
{#alert{} = Alert, State} ->
- ssl_connection:handle_own_alert(Alert, {3,4}, wait_cv, State);
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, wait_cv, State);
{State, NextState} ->
- tls_connection:next_event(NextState, no_record, State)
+ tls_gen_connection:next_event(NextState, no_record, State)
end;
-wait_cv(Type, Msg, State, Connection) ->
- ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
+wait_cv(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+wait_cv(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-wait_finished(internal, #change_cipher_spec{}, State, _Module) ->
- tls_connection:next_event(?FUNCTION_NAME, no_record, State);
+wait_finished(internal, #change_cipher_spec{}, State) ->
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
wait_finished(internal,
- #finished{} = Finished, State0, Module) ->
+ #finished{} = Finished, State0) ->
case tls_handshake_1_3:do_wait_finished(Finished, State0) of
#alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, {3,4}, finished, State0);
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, finished, State0);
State1 ->
- {Record, State} = ssl_connection:prepare_connection(State1, Module),
- tls_connection:next_event(connection, Record, State,
+ {Record, State} = ssl_gen_statem:prepare_connection(State1, tls_gen_connection),
+ tls_gen_connection:next_event(connection, Record, State,
[{{timeout, handshake}, cancel}])
end;
-wait_finished(Type, Msg, State, Connection) ->
- ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
+wait_finished(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+wait_finished(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-wait_sh(internal, #change_cipher_spec{}, State, _Module) ->
- tls_connection:next_event(?FUNCTION_NAME, no_record, State);
-wait_sh(internal, #server_hello{} = Hello, State0, _Module) ->
+wait_sh(internal, #change_cipher_spec{}, State) ->
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
+wait_sh(internal, #server_hello{extensions = Extensions} = Hello, #state{ssl_options = #{handshake := hello},
+ start_or_recv_from = From,
+ handshake_env = HsEnv} = State) ->
+ {next_state, user_hello,
+ State#state{start_or_recv_from = undefined,
+ handshake_env = HsEnv#handshake_env{
+ hello = Hello}}, [{reply, From, {ok, Extensions}}]};
+wait_sh(internal, #server_hello{} = Hello, State0) ->
case tls_handshake_1_3:do_wait_sh(Hello, State0) of
#alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, {3,4}, wait_sh, State0);
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, wait_sh, State0);
{State1, start, ServerHello} ->
%% hello_retry_request: go to start
{next_state, start, State1, [{next_event, internal, ServerHello}]};
{State1, wait_ee} ->
- tls_connection:next_event(wait_ee, no_record, State1)
+ tls_gen_connection:next_event(wait_ee, no_record, State1)
end;
-wait_sh(Type, Msg, State, Connection) ->
- ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
+wait_sh(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+wait_sh(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-wait_ee(internal, #change_cipher_spec{}, State, _Module) ->
- tls_connection:next_event(?FUNCTION_NAME, no_record, State);
-wait_ee(internal, #encrypted_extensions{} = EE, State0, _Module) ->
+wait_ee(internal, #change_cipher_spec{}, State) ->
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
+wait_ee(internal, #encrypted_extensions{} = EE, State0) ->
case tls_handshake_1_3:do_wait_ee(EE, State0) of
#alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, {3,4}, wait_ee, State0);
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, wait_ee, State0);
{State1, NextState} ->
- tls_connection:next_event(NextState, no_record, State1)
+ tls_gen_connection:next_event(NextState, no_record, State1)
end;
-wait_ee(Type, Msg, State, Connection) ->
- ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
+wait_ee(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+wait_ee(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-wait_cert_cr(internal, #change_cipher_spec{}, State, _Module) ->
- tls_connection:next_event(?FUNCTION_NAME, no_record, State);
-wait_cert_cr(internal, #certificate_1_3{} = Certificate, State0, _Module) ->
+wait_cert_cr(internal, #change_cipher_spec{}, State) ->
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
+wait_cert_cr(internal, #certificate_1_3{} = Certificate, State0) ->
case tls_handshake_1_3:do_wait_cert_cr(Certificate, State0) of
{#alert{} = Alert, State} ->
- ssl_connection:handle_own_alert(Alert, {3,4}, wait_cert_cr, State);
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, wait_cert_cr, State);
{State1, NextState} ->
- tls_connection:next_event(NextState, no_record, State1)
+ tls_gen_connection:next_event(NextState, no_record, State1)
end;
-wait_cert_cr(internal, #certificate_request_1_3{} = CertificateRequest, State0, _Module) ->
+wait_cert_cr(internal, #certificate_request_1_3{} = CertificateRequest, State0) ->
case tls_handshake_1_3:do_wait_cert_cr(CertificateRequest, State0) of
#alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, {3,4}, wait_cert_cr, State0);
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, wait_cert_cr, State0);
{State1, NextState} ->
- tls_connection:next_event(NextState, no_record, State1)
+ tls_gen_connection:next_event(NextState, no_record, State1)
end;
-wait_cert_cr(Type, Msg, State, Connection) ->
- ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
+wait_cert_cr(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+wait_cert_cr(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+
+connection(internal, #new_session_ticket{} = NewSessionTicket, State) ->
+ handle_new_session_ticket(NewSessionTicket, State),
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
+
+connection(internal, #key_update{} = KeyUpdate, State0) ->
+ case handle_key_update(KeyUpdate, State0) of
+ {ok, State} ->
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
+ {error, State, Alert} ->
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, connection, State),
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State)
+ end;
+connection({call, From}, negotiated_protocol,
+ #state{handshake_env = #handshake_env{alpn = undefined}} = State) ->
+ ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State, [{reply, From, {error, protocol_not_negotiated}}]);
+connection({call, From}, negotiated_protocol,
+ #state{handshake_env = #handshake_env{alpn = SelectedProtocol,
+ negotiated_protocol = undefined}} = State) ->
+ ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State,
+ [{reply, From, {ok, SelectedProtocol}}]);
+connection(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
+
+downgrade(Type, Event, State) ->
+ tls_connection:?FUNCTION_NAME(Type, Event, State).
+
+%--------------------------------------------------------------------
+%% internal functions
+%%--------------------------------------------------------------------
+initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trackers}, User,
+ {CbModule, DataTag, CloseTag, ErrorTag, PassiveTag}) ->
+ #{erl_dist := IsErlDist,
+ client_renegotiation := ClientRenegotiation} = SSLOptions,
+ ConnectionStates = tls_record:init_connection_states(Role, disabled),
+ InternalActiveN = case application:get_env(ssl, internal_active_n) of
+ {ok, N} when is_integer(N) andalso (not IsErlDist) ->
+ N;
+ _ ->
+ ?INTERNAL_ACTIVE_N
+ end,
+ UserMonitor = erlang:monitor(process, User),
+ InitStatEnv = #static_env{
+ role = Role,
+ transport_cb = CbModule,
+ protocol_cb = tls_gen_connection,
+ data_tag = DataTag,
+ close_tag = CloseTag,
+ error_tag = ErrorTag,
+ passive_tag = PassiveTag,
+ host = Host,
+ port = Port,
+ socket = Socket,
+ trackers = Trackers
+ },
+ #state{
+ static_env = InitStatEnv,
+ handshake_env = #handshake_env{
+ tls_handshake_history = ssl_handshake:init_handshake_history(),
+ renegotiation = {false, first},
+ allow_renegotiate = ClientRenegotiation
+ },
+ connection_env = #connection_env{user_application = {UserMonitor, User}},
+ socket_options = SocketOptions,
+ ssl_options = SSLOptions,
+ session = #session{is_resumable = false,
+ session_id = ssl_session:legacy_session_id()},
+ connection_states = ConnectionStates,
+ protocol_buffers = #protocol_buffers{},
+ user_data_buffer = {[],0,[]},
+ start_or_recv_from = undefined,
+ flight_buffer = [],
+ protocol_specific = #{sender => Sender,
+ active_n => InternalActiveN,
+ active_n_toggle => true
+ }
+ }.
+
+handle_new_session_ticket(_, #state{ssl_options = #{session_tickets := disabled}}) ->
+ ok;
+handle_new_session_ticket(#new_session_ticket{ticket_nonce = Nonce} = NewSessionTicket,
+ #state{connection_states = ConnectionStates,
+ ssl_options = #{session_tickets := SessionTickets,
+ server_name_indication := SNI},
+ connection_env = #connection_env{user_application = {_, User}}})
+ when SessionTickets =:= manual ->
+ #{security_parameters := SecParams} =
+ ssl_record:current_connection_state(ConnectionStates, read),
+ HKDF = SecParams#security_parameters.prf_algorithm,
+ RMS = SecParams#security_parameters.resumption_master_secret,
+ PSK = tls_v1:pre_shared_key(RMS, Nonce, HKDF),
+ send_ticket_data(User, NewSessionTicket, HKDF, SNI, PSK);
+handle_new_session_ticket(#new_session_ticket{ticket_nonce = Nonce} = NewSessionTicket,
+ #state{connection_states = ConnectionStates,
+ ssl_options = #{session_tickets := SessionTickets,
+ server_name_indication := SNI}})
+ when SessionTickets =:= auto ->
+ #{security_parameters := SecParams} =
+ ssl_record:current_connection_state(ConnectionStates, read),
+ HKDF = SecParams#security_parameters.prf_algorithm,
+ RMS = SecParams#security_parameters.resumption_master_secret,
+ PSK = tls_v1:pre_shared_key(RMS, Nonce, HKDF),
+ tls_client_ticket_store:store_ticket(NewSessionTicket, HKDF, SNI, PSK).
+
+
+%% Send ticket data to user as opaque binary
+send_ticket_data(User, NewSessionTicket, HKDF, SNI, PSK) ->
+ Timestamp = erlang:system_time(seconds),
+ TicketData = #{hkdf => HKDF,
+ sni => SNI,
+ psk => PSK,
+ timestamp => Timestamp,
+ ticket => NewSessionTicket},
+ User ! {ssl, session_ticket, {SNI, erlang:term_to_binary(TicketData)}}.
+
+handle_key_update(#key_update{request_update = update_not_requested}, State0) ->
+ %% Update read key in connection
+ {ok, update_cipher_key(current_read, State0)};
+handle_key_update(#key_update{request_update = update_requested},
+ #state{protocol_specific = #{sender := Sender}} = State0) ->
+ %% Update read key in connection
+ State1 = update_cipher_key(current_read, State0),
+ %% Send key_update and update sender's write key
+ case send_key_update(Sender, update_not_requested) of
+ ok ->
+ {ok, State1};
+ {error, Reason} ->
+ {error, State1, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason)}
+ end.
diff --git a/lib/ssl/src/tls_connection_sup.erl b/lib/ssl/src/tls_connection_sup.erl
index d5b228dc94..b7f80ad524 100644
--- a/lib/ssl/src/tls_connection_sup.erl
+++ b/lib/ssl/src/tls_connection_sup.erl
@@ -40,13 +40,13 @@ start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
start_link_dist() ->
- supervisor:start_link({local, ssl_connection_sup_dist}, ?MODULE, []).
+ supervisor:start_link({local, tls_dist_connection_sup}, ?MODULE, []).
start_child(Args) ->
supervisor:start_child(?MODULE, Args).
start_child_dist(Args) ->
- supervisor:start_child(ssl_connection_sup_dist, Args).
+ supervisor:start_child(tls_dist_connection_sup, Args).
%%%=========================================================================
%%% Supervisor callback
@@ -57,10 +57,10 @@ init(_O) ->
MaxT = 3600,
Name = undefined, % As simple_one_for_one is used.
- StartFunc = {tls_connection, start_link, []},
+ StartFunc = {ssl_gen_statem, start_link, []},
Restart = temporary, % E.g. should not be restarted
Shutdown = 4000,
- Modules = [tls_connection, ssl_connection],
+ Modules = [ssl_gen_statem, tls_connection, tls_connection_1_3],
Type = worker,
ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules},
diff --git a/lib/ssl/src/tls_dist_server_sup.erl b/lib/ssl/src/tls_dist_server_sup.erl
new file mode 100644
index 0000000000..96603a7495
--- /dev/null
+++ b/lib/ssl/src/tls_dist_server_sup.erl
@@ -0,0 +1,89 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2021-2021. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+-module(tls_dist_server_sup).
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/0]).
+
+%% Supervisor callback
+-export([init/1]).
+
+%%%=========================================================================
+%%% API
+%%%=========================================================================
+
+-spec start_link() -> {ok, pid()} | ignore | {error, term()}.
+
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+%%%=========================================================================
+%%% Supervisor callback
+%%%=========================================================================
+
+init([]) ->
+ ListenTracker = listen_options_tracker_child_spec(),
+ SessionTracker = tls_server_session_child_spec(),
+ Pre_1_3SessionTracker = ssl_server_session_child_spec(),
+
+ {ok, {{one_for_all, 10, 3600}, [ListenTracker,
+ SessionTracker,
+ Pre_1_3SessionTracker
+ ]}}.
+
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+
+%% Handles emulated options so that they inherited by the accept
+%% socket, even when setopts is performed on the listen socket
+listen_options_tracker_child_spec() ->
+ Name = dist_tls_socket,
+ StartFunc = {ssl_listen_tracker_sup, start_link_dist, []},
+ Restart = permanent,
+ Shutdown = 4000,
+ Modules = [ssl_listen_tracker_sup],
+ Type = supervisor,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+
+tls_server_session_child_spec() ->
+ Name = dist_tls_server_session_ticket,
+ StartFunc = {tls_server_session_ticket_sup, start_link_dist, []},
+ Restart = permanent,
+ Shutdown = 4000,
+ Modules = [tls_server_session_ticket_sup],
+ Type = supervisor,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+
+ssl_server_session_child_spec() ->
+ Name = dist_ssl_server_session_cache_sup,
+ StartFunc = {ssl_upgrade_server_session_cache_sup, start_link_dist, []},
+ Restart = permanent,
+ Shutdown = 4000,
+ Modules = [ssl_server_session_cache_sup],
+ Type = supervisor,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+
diff --git a/lib/ssl/src/tls_dist_sup.erl b/lib/ssl/src/tls_dist_sup.erl
new file mode 100644
index 0000000000..54e0a6a514
--- /dev/null
+++ b/lib/ssl/src/tls_dist_sup.erl
@@ -0,0 +1,75 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2021-2021. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+-module(tls_dist_sup).
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/0]).
+
+%% Supervisor callback
+-export([init/1]).
+
+%%%=========================================================================
+%%% API
+%%%=========================================================================
+
+-spec start_link() -> {ok, pid()} | ignore | {error, term()}.
+
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+%%%=========================================================================
+%%% Supervisor callback
+%%%=========================================================================
+
+init([]) ->
+
+ TLSConnetionSup = tls_connection_child_spec(),
+ ServerInstanceSup = server_instance_child_spec(),
+
+ {ok, {{one_for_one, 10, 3600}, [TLSConnetionSup,
+ ServerInstanceSup
+ ]}}.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+
+tls_connection_child_spec() ->
+ Name = dist_tls_connection,
+ StartFunc = {tls_connection_sup, start_link_dist, []},
+ Restart = permanent,
+ Shutdown = 4000,
+ Modules = [tls_connection_sup],
+ Type = supervisor,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+
+server_instance_child_spec() ->
+ Name = dist_tls_server_sup,
+ StartFunc = {tls_dist_server_sup, start_link, []},
+ Restart = permanent,
+ Shutdown = 4000,
+ Modules = [tls_dist_server_sup],
+ Type = supervisor,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
diff --git a/lib/ssl/src/tls_dtls_connection.erl b/lib/ssl/src/tls_dtls_connection.erl
new file mode 100644
index 0000000000..c27feadfcf
--- /dev/null
+++ b/lib/ssl/src/tls_dtls_connection.erl
@@ -0,0 +1,1687 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2013-2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Common handling of a TLS/SSL/DTLS connection, see also
+%% tls_connection.erl and dtls_connection.erl
+%%----------------------------------------------------------------------
+
+-module(tls_dtls_connection).
+
+-include_lib("public_key/include/public_key.hrl").
+-include_lib("kernel/include/logger.hrl").
+
+-include("ssl_api.hrl").
+-include("ssl_connection.hrl").
+-include("ssl_handshake.hrl").
+-include("ssl_alert.hrl").
+-include("ssl_record.hrl").
+-include("ssl_cipher.hrl").
+-include("ssl_internal.hrl").
+-include("ssl_srp.hrl").
+
+%% TLS-1.0 to TLS-1.2 Specific User Events
+-export([renegotiation/1, renegotiation/2, prf/5]).
+
+%% Data handling. Note renegotiation is replaced by sesion key update mechanism in TLS-1.3
+-export([internal_renegotiation/2]).
+
+%% Help functions for tls|dtls_connection.erl
+-export([handle_session/7,
+ handle_sni_extension/2]).
+
+%% General state handlingfor TLS-1.0 to TLS-1.2 and gen_handshake that wrapps
+%% handling of common state handling for handshake messages for error handling
+-export([hello/3,
+ user_hello/3,
+ abbreviated/3,
+ certify/3,
+ wait_ocsp_stapling/3,
+ cipher/3,
+ connection/3,
+ downgrade/3,
+ gen_handshake/4]).
+
+%%--------------------------------------------------------------------
+-spec internal_renegotiation(pid(), ssl_record:connection_states()) ->
+ ok.
+%%
+%% Description: Starts a renegotiation of the ssl session.
+%%--------------------------------------------------------------------
+internal_renegotiation(ConnectionPid, #{current_write := WriteState}) ->
+ gen_statem:cast(ConnectionPid, {internal_renegotiate, WriteState}).
+
+%%====================================================================
+%% User events
+%%====================================================================
+
+%%--------------------------------------------------------------------
+-spec renegotiation(pid()) -> ok | {error, reason()}.
+%%
+%% Description: Starts a renegotiation of the ssl session.
+%%--------------------------------------------------------------------
+renegotiation(ConnectionPid) ->
+ ssl_gen_statem:call(ConnectionPid, renegotiate).
+
+renegotiation(Pid, WriteState) ->
+ ssl_gen_statem:call(Pid, {user_renegotiate, WriteState}).
+
+%%--------------------------------------------------------------------
+-spec prf(pid(), binary() | 'master_secret', binary(),
+ [binary() | ssl:prf_random()], non_neg_integer()) ->
+ {ok, binary()} | {error, reason()} | {'EXIT', term()}.
+%%
+%% Description: use a ssl sessions TLS PRF to generate key material
+%%--------------------------------------------------------------------
+prf(ConnectionPid, Secret, Label, Seed, WantedLength) ->
+ ssl_gen_statem:call(ConnectionPid, {prf, Secret, Label, Seed, WantedLength}).
+
+%%====================================================================
+%% Help functions for tls|dtls_connection.erl
+%%====================================================================
+%%--------------------------------------------------------------------
+-spec handle_session(#server_hello{}, ssl_record:ssl_version(),
+ binary(), ssl_record:connection_states(), _,_, #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+handle_session(#server_hello{cipher_suite = CipherSuite,
+ compression_method = Compression},
+ Version, NewId, ConnectionStates, ProtoExt, Protocol0,
+ #state{session = #session{session_id = OldId},
+ handshake_env = #handshake_env{negotiated_protocol = CurrentProtocol} = HsEnv,
+ connection_env = #connection_env{negotiated_version = ReqVersion} = CEnv} = State0) ->
+ #{key_exchange := KeyAlgorithm} =
+ ssl_cipher_format:suite_bin_to_map(CipherSuite),
+
+ PremasterSecret = make_premaster_secret(ReqVersion, KeyAlgorithm),
+
+ {ExpectNPN, Protocol} = case Protocol0 of
+ undefined ->
+
+ {false, CurrentProtocol};
+ _ ->
+ {ProtoExt =:= npn, Protocol0}
+ end,
+
+ State = State0#state{connection_states = ConnectionStates,
+ handshake_env = HsEnv#handshake_env{kex_algorithm = KeyAlgorithm,
+ premaster_secret = PremasterSecret,
+ expecting_next_protocol_negotiation = ExpectNPN,
+ negotiated_protocol = Protocol},
+ connection_env = CEnv#connection_env{negotiated_version = Version}},
+
+ case ssl_session:is_new(OldId, NewId) of
+ true ->
+ handle_new_session(NewId, CipherSuite, Compression,
+ State#state{connection_states = ConnectionStates});
+ false ->
+ handle_resumed_session(NewId,
+ State#state{connection_states = ConnectionStates})
+ end.
+
+
+%%====================================================================
+%% gen_statem general state functions with connection cb argument
+%%====================================================================
+
+%%--------------------------------------------------------------------
+-spec hello(gen_statem:event_type(),
+ #hello_request{} | #server_hello{} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+hello({call, From}, Msg, State) ->
+ handle_call(Msg, From, ?FUNCTION_NAME, State);
+hello(internal, {common_client_hello, Type, ServerHelloExt}, State) ->
+ do_server_hello(Type, ServerHelloExt, State);
+hello(info, Msg, State) ->
+ handle_info(Msg, ?FUNCTION_NAME, State);
+hello(internal, #hello_request{}, _) ->
+ keep_state_and_data;
+hello(Type, Event, State) ->
+ ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State).
+
+%%--------------------------------------------------------------------
+-spec user_hello(gen_statem:event_type(),
+ #hello_request{} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+user_hello({call, From}, cancel, #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
+ gen_statem:reply(From, ok),
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled),
+ Version, ?FUNCTION_NAME, State);
+user_hello({call, From}, {handshake_continue, NewOptions, Timeout},
+ #state{static_env = #static_env{role = Role},
+ handshake_env = #handshake_env{hello = Hello},
+ ssl_options = Options0} = State0) ->
+ Options = ssl:handle_options(NewOptions, Role, Options0#{handshake => full}),
+ State = ssl_gen_statem:ssl_config(Options, Role, State0),
+ {next_state, hello, State#state{start_or_recv_from = From},
+ [{next_event, internal, Hello}, {{timeout, handshake}, Timeout, close}]};
+user_hello(_, _, _) ->
+ {keep_state_and_data, [postpone]}.
+
+%%--------------------------------------------------------------------
+-spec abbreviated(gen_statem:event_type(),
+ #hello_request{} | #finished{} | term(),
+ #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+abbreviated({call, From}, Msg, State) ->
+ handle_call(Msg, From, ?FUNCTION_NAME, State);
+abbreviated(internal, #finished{verify_data = Data} = Finished,
+ #state{static_env = #static_env{role = server,
+ protocol_cb = Connection},
+ handshake_env = #handshake_env{tls_handshake_history = Hist,
+ expecting_finished = true} = HsEnv,
+ connection_env = #connection_env{negotiated_version = Version},
+ session = #session{master_secret = MasterSecret},
+ connection_states = ConnectionStates0} =
+ State0) ->
+ case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, client,
+ get_current_prf(ConnectionStates0, write),
+ MasterSecret, Hist) of
+ verified ->
+ ConnectionStates =
+ ssl_record:set_client_verify_data(current_both, Data, ConnectionStates0),
+ {Record, State} =
+ ssl_gen_statem:prepare_connection(State0#state{connection_states = ConnectionStates,
+ handshake_env = HsEnv#handshake_env{expecting_finished = false}},
+ Connection),
+ Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close}]);
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
+ end;
+abbreviated(internal, #finished{verify_data = Data} = Finished,
+ #state{static_env = #static_env{role = client,
+ protocol_cb = Connection},
+ handshake_env = #handshake_env{tls_handshake_history = Hist0},
+ connection_env = #connection_env{negotiated_version = Version},
+ session = #session{master_secret = MasterSecret},
+ connection_states = ConnectionStates0} = State0) ->
+ case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, server,
+ get_pending_prf(ConnectionStates0, write),
+ MasterSecret, Hist0) of
+ verified ->
+ ConnectionStates1 =
+ ssl_record:set_server_verify_data(current_read, Data, ConnectionStates0),
+ {#state{handshake_env = HsEnv} = State1, Actions} =
+ finalize_handshake(State0#state{connection_states = ConnectionStates1},
+ ?FUNCTION_NAME, Connection),
+ {Record, State} =
+ ssl_gen_statem:prepare_connection(State1#state{handshake_env = HsEnv#handshake_env{expecting_finished = false}},
+ Connection),
+ Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close} | Actions]);
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
+ end;
+%% only allowed to send next_protocol message after change cipher spec
+%% & before finished message and it is not allowed during renegotiation
+abbreviated(internal, #next_protocol{selected_protocol = SelectedProtocol},
+ #state{static_env = #static_env{role = server,
+ protocol_cb = Connection},
+ handshake_env = #handshake_env{expecting_next_protocol_negotiation = true} = HsEnv} = State) ->
+ Connection:next_event(?FUNCTION_NAME, no_record,
+ State#state{handshake_env = HsEnv#handshake_env{negotiated_protocol = SelectedProtocol,
+ expecting_next_protocol_negotiation = false}});
+abbreviated(internal,
+ #change_cipher_spec{type = <<1>>},
+ #state{static_env = #static_env{protocol_cb = Connection},
+ connection_states = ConnectionStates0,
+ handshake_env = HsEnv} = State) ->
+ ConnectionStates1 =
+ ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection),
+ Connection:next_event(?FUNCTION_NAME, no_record, State#state{connection_states =
+ ConnectionStates1,
+ handshake_env = HsEnv#handshake_env{expecting_finished = true}});
+abbreviated(info, Msg, State) ->
+ handle_info(Msg, ?FUNCTION_NAME, State);
+abbreviated(internal, #hello_request{}, _) ->
+ keep_state_and_data;
+abbreviated(Type, Event, State) ->
+ ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State).
+
+%%--------------------------------------------------------------------
+-spec wait_ocsp_stapling(gen_statem:event_type(),
+ #certificate{} | #certificate_status{} | term(),
+ #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_ocsp_stapling(internal, #certificate{}, #state{static_env = #static_env{protocol_cb = Connection}} = State) ->
+ %% Postpone message, should be handled in certify after receiving staple message
+ Connection:next_event(?FUNCTION_NAME, no_record, State, [{postpone, true}]);
+%% Receive OCSP staple message
+wait_ocsp_stapling(internal, #certificate_status{} = CertStatus,
+ #state{static_env = #static_env{protocol_cb = Connection},
+ handshake_env = #handshake_env{
+ ocsp_stapling_state = OcspState} = HsEnv} = State) ->
+ Connection:next_event(certify, no_record,
+ State#state{handshake_env = HsEnv#handshake_env{ocsp_stapling_state =
+ OcspState#{ocsp_expect => stapled,
+ ocsp_response => CertStatus}}});
+%% Server did not send OCSP staple message
+wait_ocsp_stapling(internal, Msg, #state{static_env = #static_env{protocol_cb = Connection},
+ handshake_env = #handshake_env{
+ ocsp_stapling_state = OcspState} = HsEnv} = State)
+ when is_record(Msg, server_key_exchange) orelse
+ is_record(Msg, hello_request) orelse
+ is_record(Msg, certificate_request) orelse
+ is_record(Msg, server_hello_done) orelse
+ is_record(Msg, client_key_exchange) ->
+ Connection:next_event(certify, no_record,
+ State#state{handshake_env =
+ HsEnv#handshake_env{ocsp_stapling_state = OcspState#{ocsp_expect => undetermined}}},
+ [{postpone, true}]);
+wait_ocsp_stapling(internal, #hello_request{}, _) ->
+ keep_state_and_data;
+wait_ocsp_stapling(Type, Event, State) ->
+ ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State).
+
+%%--------------------------------------------------------------------
+-spec certify(gen_statem:event_type(),
+ #hello_request{} | #certificate{} | #server_key_exchange{} |
+ #certificate_request{} | #server_hello_done{} | #client_key_exchange{} | term(),
+ #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+certify({call, From}, Msg, State) ->
+ handle_call(Msg, From, ?FUNCTION_NAME, State);
+certify(info, Msg, State) ->
+ handle_info(Msg, ?FUNCTION_NAME, State);
+certify(internal, #certificate{asn1_certificates = []},
+ #state{static_env = #static_env{role = server},
+ connection_env = #connection_env{negotiated_version = Version},
+ ssl_options = #{verify := verify_peer,
+ fail_if_no_peer_cert := true}} =
+ State) ->
+ Alert = ?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, no_client_certificate_provided),
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State);
+certify(internal, #certificate{asn1_certificates = []},
+ #state{static_env = #static_env{role = server,
+ protocol_cb = Connection},
+ ssl_options = #{verify := verify_peer,
+ fail_if_no_peer_cert := false}} =
+ State0) ->
+ Connection:next_event(?FUNCTION_NAME, no_record, State0#state{client_certificate_requested = false});
+certify(internal, #certificate{},
+ #state{static_env = #static_env{role = server},
+ connection_env = #connection_env{negotiated_version = Version},
+ ssl_options = #{verify := verify_none}} =
+ State) ->
+ Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, unrequested_certificate),
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State);
+certify(internal, #certificate{},
+ #state{static_env = #static_env{protocol_cb = Connection},
+ handshake_env = #handshake_env{
+ ocsp_stapling_state = #{ocsp_expect := staple}}} = State) ->
+ Connection:next_event(wait_ocsp_stapling, no_record, State, [{postpone, true}]);
+certify(internal, #certificate{asn1_certificates = [Peer|_]} = Cert,
+ #state{static_env = #static_env{
+ role = Role,
+ host = Host,
+ protocol_cb = Connection,
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef,
+ crl_db = CRLDbInfo},
+ handshake_env = #handshake_env{
+ ocsp_stapling_state = #{ocsp_expect := Status} = OcspState},
+ connection_env = #connection_env{
+ negotiated_version = Version},
+ ssl_options = Opts} = State) when Status =/= staple ->
+ OcspInfo = ocsp_info(OcspState, Opts, Peer),
+ case ssl_handshake:certify(Cert, CertDbHandle, CertDbRef,
+ Opts, CRLDbInfo, Role, Host,
+ ensure_tls(Version), OcspInfo) of
+ {PeerCert, PublicKeyInfo} ->
+ handle_peer_cert(Role, PeerCert, PublicKeyInfo,
+ State#state{client_certificate_requested = false}, Connection, []);
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)
+ end;
+certify(internal, #server_key_exchange{exchange_keys = Keys},
+ #state{static_env = #static_env{role = client,
+ protocol_cb = Connection},
+ handshake_env = #handshake_env{kex_algorithm = KexAlg,
+ public_key_info = PubKeyInfo} = HsEnv,
+ connection_env = #connection_env{negotiated_version = Version},
+ session = Session,
+ connection_states = ConnectionStates} = State)
+ when KexAlg == dhe_dss;
+ KexAlg == dhe_rsa;
+ KexAlg == ecdhe_rsa;
+ KexAlg == ecdhe_ecdsa;
+ KexAlg == dh_anon;
+ KexAlg == ecdh_anon;
+ KexAlg == psk;
+ KexAlg == dhe_psk;
+ KexAlg == ecdhe_psk;
+ KexAlg == rsa_psk;
+ KexAlg == srp_dss;
+ KexAlg == srp_rsa;
+ KexAlg == srp_anon ->
+
+ Params = ssl_handshake:decode_server_key(Keys, KexAlg, ssl:tls_version(Version)),
+
+ %% Use negotiated value if TLS-1.2 otherwhise return default
+ HashSign = negotiated_hashsign(Params#server_key_params.hashsign, KexAlg, PubKeyInfo, ssl:tls_version(Version)),
+
+ case is_anonymous(KexAlg) of
+ true ->
+ calculate_secret(Params#server_key_params.params,
+ State#state{handshake_env = HsEnv#handshake_env{hashsign_algorithm = HashSign}}, Connection);
+ false ->
+ case ssl_handshake:verify_server_key(Params, HashSign,
+ ConnectionStates, ssl:tls_version(Version), PubKeyInfo) of
+ true ->
+ calculate_secret(Params#server_key_params.params,
+ State#state{handshake_env = HsEnv#handshake_env{hashsign_algorithm = HashSign},
+ session = session_handle_params(Params#server_key_params.params, Session)},
+ Connection);
+ false ->
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?DECRYPT_ERROR),
+ Version, ?FUNCTION_NAME, State)
+ end
+ end;
+certify(internal, #certificate_request{},
+ #state{static_env = #static_env{role = client},
+ handshake_env = #handshake_env{kex_algorithm = KexAlg},
+ connection_env = #connection_env{negotiated_version = Version}} = State)
+ when KexAlg == dh_anon;
+ KexAlg == ecdh_anon;
+ KexAlg == psk;
+ KexAlg == dhe_psk;
+ KexAlg == ecdhe_psk;
+ KexAlg == rsa_psk;
+ KexAlg == srp_dss;
+ KexAlg == srp_rsa;
+ KexAlg == srp_anon ->
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE),
+ Version, ?FUNCTION_NAME, State);
+certify(internal, #certificate_request{},
+ #state{static_env = #static_env{role = client,
+ protocol_cb = Connection},
+ session = #session{own_certificates = undefined}} = State) ->
+ %% The client does not have a certificate and will send an empty reply, the server may fail
+ %% or accept the connection by its own preference. No signature algorihms needed as there is
+ %% no certificate to verify.
+ Connection:next_event(?FUNCTION_NAME, no_record, State#state{client_certificate_requested = true});
+certify(internal, #certificate_request{} = CertRequest,
+ #state{static_env = #static_env{role = client,
+ protocol_cb = Connection},
+ handshake_env = HsEnv,
+ connection_env = #connection_env{negotiated_version = Version},
+ session = #session{own_certificates = [Cert|_]},
+ ssl_options = #{signature_algs := SupportedHashSigns}} = State) ->
+ case ssl_handshake:select_hashsign(CertRequest, Cert,
+ SupportedHashSigns, ssl:tls_version(Version)) of
+ #alert {} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State);
+ NegotiatedHashSign ->
+ Connection:next_event(?FUNCTION_NAME, no_record,
+ State#state{client_certificate_requested = true,
+ handshake_env = HsEnv#handshake_env{cert_hashsign_algorithm = NegotiatedHashSign}})
+ end;
+%% PSK and RSA_PSK might bypass the Server-Key-Exchange
+certify(internal, #server_hello_done{},
+ #state{static_env = #static_env{role = client,
+ protocol_cb = Connection},
+ session = #session{master_secret = undefined},
+ connection_env = #connection_env{negotiated_version = Version},
+ handshake_env = #handshake_env{kex_algorithm = KexAlg,
+ premaster_secret = undefined,
+ server_psk_identity = PSKIdentity} = HsEnv,
+ ssl_options = #{user_lookup_fun := PSKLookup}} = State0)
+ when KexAlg == psk ->
+ case ssl_handshake:premaster_secret({KexAlg, PSKIdentity}, PSKLookup) of
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0);
+ PremasterSecret ->
+ State = master_secret(PremasterSecret,
+ State0#state{handshake_env =
+ HsEnv#handshake_env{premaster_secret = PremasterSecret}}),
+ client_certify_and_key_exchange(State, Connection)
+ end;
+certify(internal, #server_hello_done{},
+ #state{static_env = #static_env{role = client,
+ protocol_cb = Connection},
+ connection_env = #connection_env{negotiated_version = {Major, Minor}} = Version,
+ handshake_env = #handshake_env{kex_algorithm = KexAlg,
+ premaster_secret = undefined,
+ server_psk_identity = PSKIdentity} = HsEnv,
+ session = #session{master_secret = undefined},
+ ssl_options = #{user_lookup_fun := PSKLookup}} = State0)
+ when KexAlg == rsa_psk ->
+ Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2),
+ RSAPremasterSecret = <<?BYTE(Major), ?BYTE(Minor), Rand/binary>>,
+ case ssl_handshake:premaster_secret({KexAlg, PSKIdentity}, PSKLookup,
+ RSAPremasterSecret) of
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0);
+ PremasterSecret ->
+ State = master_secret(PremasterSecret,
+ State0#state{handshake_env =
+ HsEnv#handshake_env{premaster_secret = RSAPremasterSecret}}),
+ client_certify_and_key_exchange(State, Connection)
+ end;
+%% Master secret was determined with help of server-key exchange msg
+certify(internal, #server_hello_done{},
+ #state{static_env = #static_env{role = client,
+ protocol_cb = Connection},
+ connection_env = #connection_env{negotiated_version = Version},
+ handshake_env = #handshake_env{premaster_secret = undefined},
+ session = #session{master_secret = MasterSecret} = Session,
+ connection_states = ConnectionStates0} = State0) ->
+ case ssl_handshake:master_secret(ssl:tls_version(Version), Session,
+ ConnectionStates0, client) of
+ {MasterSecret, ConnectionStates} ->
+ State = State0#state{connection_states = ConnectionStates},
+ client_certify_and_key_exchange(State, Connection);
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
+ end;
+%% Master secret is calculated from premaster_secret
+certify(internal, #server_hello_done{},
+ #state{static_env = #static_env{role = client,
+ protocol_cb = Connection},
+ connection_env = #connection_env{negotiated_version = Version},
+ handshake_env = #handshake_env{premaster_secret = PremasterSecret},
+ session = Session0,
+ connection_states = ConnectionStates0} = State0) ->
+ case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret,
+ ConnectionStates0, client) of
+ {MasterSecret, ConnectionStates} ->
+ Session = Session0#session{master_secret = MasterSecret},
+ State = State0#state{connection_states = ConnectionStates,
+ session = Session},
+ client_certify_and_key_exchange(State, Connection);
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
+ end;
+certify(internal = Type, #client_key_exchange{} = Msg,
+ #state{static_env = #static_env{role = server},
+ client_certificate_requested = true,
+ connection_env = #connection_env{negotiated_version = Version},
+ ssl_options = #{fail_if_no_peer_cert := true}} = State) ->
+ %% We expect a certificate here
+ Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, {unexpected_msg, {Type, Msg}}),
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State);
+certify(internal, #client_key_exchange{exchange_keys = Keys},
+ State = #state{handshake_env = #handshake_env{kex_algorithm = KeyAlg},
+ static_env = #static_env{protocol_cb = Connection},
+ connection_env = #connection_env{negotiated_version = Version}}) ->
+ try
+ certify_client_key_exchange(ssl_handshake:decode_client_key(Keys, KeyAlg, ssl:tls_version(Version)),
+ State, Connection)
+ catch
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)
+ end;
+certify(internal, #hello_request{}, _) ->
+ keep_state_and_data;
+certify(Type, Event, State) ->
+ ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State).
+
+%%--------------------------------------------------------------------
+-spec cipher(gen_statem:event_type(),
+ #hello_request{} | #certificate_verify{} | #finished{} | term(),
+ #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+cipher({call, From}, Msg, State) ->
+ handle_call(Msg, From, ?FUNCTION_NAME, State);
+cipher(info, Msg, State) ->
+ handle_info(Msg, ?FUNCTION_NAME, State);
+cipher(internal, #certificate_verify{signature = Signature,
+ hashsign_algorithm = CertHashSign},
+ #state{static_env = #static_env{role = server,
+ protocol_cb = Connection},
+ handshake_env = #handshake_env{tls_handshake_history = Hist,
+ kex_algorithm = KexAlg,
+ public_key_info = PubKeyInfo} = HsEnv,
+ connection_env = #connection_env{negotiated_version = Version},
+ session = #session{master_secret = MasterSecret}
+ } = State) ->
+
+ TLSVersion = ssl:tls_version(Version),
+ %% Use negotiated value if TLS-1.2 otherwhise return default
+ HashSign = negotiated_hashsign(CertHashSign, KexAlg, PubKeyInfo, TLSVersion),
+ case ssl_handshake:certificate_verify(Signature, PubKeyInfo,
+ TLSVersion, HashSign, MasterSecret, Hist) of
+ valid ->
+ Connection:next_event(?FUNCTION_NAME, no_record,
+ State#state{handshake_env = HsEnv#handshake_env{cert_hashsign_algorithm = HashSign}});
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)
+ end;
+%% client must send a next protocol message if we are expecting it
+cipher(internal, #finished{},
+ #state{static_env = #static_env{role = server},
+ handshake_env = #handshake_env{expecting_next_protocol_negotiation = true,
+ negotiated_protocol = undefined},
+ connection_env = #connection_env{negotiated_version = Version}} = State0) ->
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), Version, ?FUNCTION_NAME, State0);
+cipher(internal, #finished{verify_data = Data} = Finished,
+ #state{static_env = #static_env{role = Role,
+ host = Host,
+ port = Port,
+ trackers = Trackers},
+ handshake_env = #handshake_env{tls_handshake_history = Hist,
+ expecting_finished = true} = HsEnv,
+ connection_env = #connection_env{negotiated_version = Version},
+ session = #session{master_secret = MasterSecret}
+ = Session0,
+ ssl_options = SslOpts,
+ connection_states = ConnectionStates0} = State) ->
+ case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished,
+ opposite_role(Role),
+ get_current_prf(ConnectionStates0, read),
+ MasterSecret, Hist) of
+ verified ->
+ Session = handle_session(Role, SslOpts, Host, Port, Trackers, Session0),
+ cipher_role(Role, Data, Session,
+ State#state{handshake_env = HsEnv#handshake_env{expecting_finished = false}});
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)
+ end;
+%% only allowed to send next_protocol message after change cipher spec
+%% & before finished message and it is not allowed during renegotiation
+cipher(internal, #next_protocol{selected_protocol = SelectedProtocol},
+ #state{static_env = #static_env{role = server, protocol_cb = Connection},
+ handshake_env = #handshake_env{expecting_finished = true,
+ expecting_next_protocol_negotiation = true} = HsEnv} = State) ->
+ Connection:next_event(?FUNCTION_NAME, no_record,
+ State#state{handshake_env = HsEnv#handshake_env{negotiated_protocol = SelectedProtocol,
+ expecting_next_protocol_negotiation = false}});
+cipher(internal, #change_cipher_spec{type = <<1>>},
+ #state{handshake_env = HsEnv,
+ static_env = #static_env{protocol_cb = Connection},
+ connection_states = ConnectionStates0} = State) ->
+ ConnectionStates =
+ ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection),
+ Connection:next_event(?FUNCTION_NAME, no_record, State#state{handshake_env = HsEnv#handshake_env{expecting_finished = true},
+ connection_states = ConnectionStates});
+cipher(internal, #hello_request{}, _) ->
+ keep_state_and_data;
+cipher(Type, Event, State) ->
+ ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State).
+
+%%--------------------------------------------------------------------
+-spec connection(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+connection({call, From}, renegotiate, #state{static_env = #static_env{protocol_cb = tls_gen_connection},
+ handshake_env = HsEnv} = State) ->
+ tls_connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, From}}}, []);
+connection({call, From}, renegotiate, #state{static_env = #static_env{protocol_cb = dtls_gen_connection},
+ handshake_env = HsEnv} = State) ->
+ dtls_connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, From}}}, []);
+connection({call, From}, negotiated_protocol,
+ #state{handshake_env = #handshake_env{alpn = undefined,
+ negotiated_protocol = undefined}} = State) ->
+ ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State, [{reply, From, {error, protocol_not_negotiated}}]);
+connection({call, From}, negotiated_protocol,
+ #state{handshake_env = #handshake_env{alpn = undefined,
+ negotiated_protocol = SelectedProtocol}} = State) ->
+ ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State,
+ [{reply, From, {ok, SelectedProtocol}}]);
+connection({call, From}, negotiated_protocol,
+ #state{handshake_env = #handshake_env{alpn = SelectedProtocol,
+ negotiated_protocol = undefined}} = State) ->
+ ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State,
+ [{reply, From, {ok, SelectedProtocol}}]);
+connection(cast, {internal_renegotiate, WriteState}, #state{static_env = #static_env{protocol_cb = tls_gen_connection},
+ handshake_env = HsEnv,
+ connection_states = ConnectionStates}
+ = State) ->
+ tls_connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, internal}},
+ connection_states = ConnectionStates#{current_write => WriteState}}, []);
+connection(cast, {internal_renegotiate, WriteState}, #state{static_env = #static_env{protocol_cb = dtls_gen_connection},
+ handshake_env = HsEnv,
+ connection_states = ConnectionStates}
+ = State) ->
+ dtls_connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, internal}},
+ connection_states = ConnectionStates#{current_write => WriteState}}, []);
+
+connection(internal, {handshake, {#hello_request{} = Handshake, _}},
+ #state{handshake_env = HsEnv} = State) ->
+ %% Should not be included in handshake history
+ {next_state, ?FUNCTION_NAME, State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, peer}}},
+ [{next_event, internal, Handshake}]};
+connection(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec downgrade(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+downgrade(Type, Event, State) ->
+ ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State).
+
+gen_handshake(StateName, Type, Event,
+ #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
+ try tls_dtls_connection:StateName(Type, Event, State) of
+ Result ->
+ Result
+ catch
+ _:_ ->
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
+ malformed_handshake_data),
+ Version, StateName, State)
+ end.
+
+%%--------------------------------------------------------------------
+%% Event handling functions called by state functions to handle
+%% common or unexpected events for the state.
+%%--------------------------------------------------------------------
+handle_call(renegotiate, From, StateName, _) when StateName =/= connection ->
+ {keep_state_and_data, [{reply, From, {error, already_renegotiating}}]};
+
+handle_call({prf, Secret, Label, Seed, WantedLength}, From, _,
+ #state{connection_states = ConnectionStates,
+ connection_env = #connection_env{negotiated_version = Version}}) ->
+ #{security_parameters := SecParams} =
+ ssl_record:current_connection_state(ConnectionStates, read),
+ #security_parameters{master_secret = MasterSecret,
+ client_random = ClientRandom,
+ server_random = ServerRandom,
+ prf_algorithm = PRFAlgorithm} = SecParams,
+ Reply = try
+ SecretToUse = case Secret of
+ _ when is_binary(Secret) -> Secret;
+ master_secret -> MasterSecret
+ end,
+ SeedToUse = lists:reverse(
+ lists:foldl(fun(X, Acc) when is_binary(X) -> [X|Acc];
+ (client_random, Acc) -> [ClientRandom|Acc];
+ (server_random, Acc) -> [ServerRandom|Acc]
+ end, [], Seed)),
+ ssl_handshake:prf(ssl:tls_version(Version), PRFAlgorithm, SecretToUse, Label, SeedToUse, WantedLength)
+ catch
+ exit:_ -> {error, badarg};
+ error:Reason -> {error, Reason}
+ end,
+ {keep_state_and_data, [{reply, From, Reply}]};
+handle_call(Msg, From, StateName, State) ->
+ ssl_gen_statem:handle_call(Msg, From, StateName, State).
+
+handle_info(Msg, StateName, State) ->
+ ssl_gen_statem:handle_info(Msg, StateName, State).
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+do_server_hello(Type, #{next_protocol_negotiation := NextProtocols} =
+ ServerHelloExt,
+ #state{connection_env = #connection_env{negotiated_version = Version},
+ static_env = #static_env{protocol_cb = Connection},
+ handshake_env = HsEnv,
+ session = #session{session_id = SessId},
+ connection_states = ConnectionStates0,
+ ssl_options = #{versions := [HighestVersion|_]}}
+ = State0) when is_atom(Type) ->
+ %% TLS 1.3 - Section 4.1.3
+ %% Override server random values for TLS 1.3 downgrade protection mechanism.
+ ConnectionStates1 = update_server_random(ConnectionStates0, Version, HighestVersion),
+ State1 = State0#state{connection_states = ConnectionStates1},
+ ServerHello =
+ ssl_handshake:server_hello(SessId, ssl:tls_version(Version),
+ ConnectionStates1, ServerHelloExt),
+ State = server_hello(ServerHello,
+ State1#state{handshake_env = HsEnv#handshake_env{expecting_next_protocol_negotiation =
+ NextProtocols =/= undefined}}, Connection),
+ case Type of
+ new ->
+ new_server_hello(ServerHello, State, Connection);
+ resumed ->
+ resumed_server_hello(State, Connection)
+ end.
+
+update_server_random(#{pending_read := #{security_parameters := ReadSecParams0} =
+ ReadState0,
+ pending_write := #{security_parameters := WriteSecParams0} =
+ WriteState0} = ConnectionStates,
+ Version, HighestVersion) ->
+ ReadRandom = override_server_random(
+ ReadSecParams0#security_parameters.server_random,
+ Version,
+ HighestVersion),
+ WriteRandom = override_server_random(
+ WriteSecParams0#security_parameters.server_random,
+ Version,
+ HighestVersion),
+ ReadSecParams = ReadSecParams0#security_parameters{server_random = ReadRandom},
+ WriteSecParams = WriteSecParams0#security_parameters{server_random = WriteRandom},
+ ReadState = ReadState0#{security_parameters => ReadSecParams},
+ WriteState = WriteState0#{security_parameters => WriteSecParams},
+
+ ConnectionStates#{pending_read => ReadState, pending_write => WriteState}.
+
+%% TLS 1.3 - Section 4.1.3
+%%
+%% If negotiating TLS 1.2, TLS 1.3 servers MUST set the last eight bytes
+%% of their Random value to the bytes:
+%%
+%% 44 4F 57 4E 47 52 44 01
+%%
+%% If negotiating TLS 1.1 or below, TLS 1.3 servers MUST and TLS 1.2
+%% servers SHOULD set the last eight bytes of their Random value to the
+%% bytes:
+%%
+%% 44 4F 57 4E 47 52 44 00
+override_server_random(<<Random0:24/binary,_:8/binary>> = Random, {M,N}, {Major,Minor})
+ when Major > 3 orelse Major =:= 3 andalso Minor >= 4 -> %% TLS 1.3 or above
+ if M =:= 3 andalso N =:= 3 -> %% Negotating TLS 1.2
+ Down = ?RANDOM_OVERRIDE_TLS12,
+ <<Random0/binary,Down/binary>>;
+ M =:= 3 andalso N < 3 -> %% Negotating TLS 1.1 or prior
+ Down = ?RANDOM_OVERRIDE_TLS11,
+ <<Random0/binary,Down/binary>>;
+ true ->
+ Random
+ end;
+override_server_random(<<Random0:24/binary,_:8/binary>> = Random, {M,N}, {Major,Minor})
+ when Major =:= 3 andalso Minor =:= 3 -> %% TLS 1.2
+ if M =:= 3 andalso N < 3 -> %% Negotating TLS 1.1 or prior
+ Down = ?RANDOM_OVERRIDE_TLS11,
+ <<Random0/binary,Down/binary>>;
+ true ->
+ Random
+ end;
+override_server_random(Random, _, _) ->
+ Random.
+
+new_server_hello(#server_hello{cipher_suite = CipherSuite,
+ compression_method = Compression,
+ session_id = SessionId},
+ #state{session = Session0,
+ static_env = #static_env{protocol_cb = Connection},
+ connection_env = #connection_env{negotiated_version = Version}} = State0, Connection) ->
+ try server_certify_and_key_exchange(State0, Connection) of
+ #state{} = State1 ->
+ {State, Actions} = server_hello_done(State1, Connection),
+ Session =
+ Session0#session{session_id = SessionId,
+ cipher_suite = CipherSuite,
+ compression_method = Compression},
+ Connection:next_event(certify, no_record, State#state{session = Session}, Actions)
+ catch
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, hello, State0)
+ end.
+
+resumed_server_hello(#state{session = Session,
+ connection_states = ConnectionStates0,
+ static_env = #static_env{protocol_cb = Connection},
+ connection_env = #connection_env{negotiated_version = Version}} = State0, Connection) ->
+
+ case ssl_handshake:master_secret(ssl:tls_version(Version), Session,
+ ConnectionStates0, server) of
+ {_, ConnectionStates1} ->
+ State1 = State0#state{connection_states = ConnectionStates1,
+ session = Session},
+ {State, Actions} =
+ finalize_handshake(State1, abbreviated, Connection),
+ Connection:next_event(abbreviated, no_record, State, Actions);
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, hello, State0)
+ end.
+
+server_hello(ServerHello, State0, Connection) ->
+ CipherSuite = ServerHello#server_hello.cipher_suite,
+ #{key_exchange := KeyAlgorithm} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
+ #state{handshake_env = HsEnv} = State = Connection:queue_handshake(ServerHello, State0),
+ State#state{handshake_env = HsEnv#handshake_env{kex_algorithm = KeyAlgorithm}}.
+
+server_hello_done(State, Connection) ->
+ HelloDone = ssl_handshake:server_hello_done(),
+ Connection:send_handshake(HelloDone, State).
+
+handle_peer_cert(Role, PeerCert, PublicKeyInfo,
+ #state{handshake_env = HsEnv,
+ static_env = #static_env{protocol_cb = Connection},
+ session = #session{cipher_suite = CipherSuite} = Session} = State0,
+ Connection, Actions) ->
+ State1 = State0#state{handshake_env = HsEnv#handshake_env{public_key_info = PublicKeyInfo},
+ session =
+ Session#session{peer_certificate = PeerCert}},
+ #{key_exchange := KeyAlgorithm} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
+ State = handle_peer_cert_key(Role, PeerCert, PublicKeyInfo, KeyAlgorithm, State1),
+ Connection:next_event(certify, no_record, State, Actions).
+
+handle_peer_cert_key(client, _,
+ {?'id-ecPublicKey', #'ECPoint'{point = _ECPoint} = PublicKey,
+ PublicKeyParams},
+ KeyAlg, #state{handshake_env = HsEnv,
+ session = Session} = State) when KeyAlg == ecdh_rsa;
+ KeyAlg == ecdh_ecdsa ->
+ ECDHKey = public_key:generate_key(PublicKeyParams),
+ PremasterSecret = ssl_handshake:premaster_secret(PublicKey, ECDHKey),
+ master_secret(PremasterSecret, State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKey},
+ session = Session#session{ecc = PublicKeyParams}});
+handle_peer_cert_key(_, _, _, _, State) ->
+ State.
+
+certify_client(#state{static_env = #static_env{role = client,
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef},
+ client_certificate_requested = true,
+ session = #session{own_certificates = OwnCerts}}
+ = State, Connection) ->
+ Certificate = ssl_handshake:certificate(OwnCerts, CertDbHandle, CertDbRef, client),
+ Connection:queue_handshake(Certificate, State);
+certify_client(#state{client_certificate_requested = false} = State, _) ->
+ State.
+
+verify_client_cert(#state{static_env = #static_env{role = client},
+ handshake_env = #handshake_env{tls_handshake_history = Hist,
+ cert_hashsign_algorithm = HashSign},
+ connection_env = #connection_env{negotiated_version = Version,
+ private_key = PrivateKey},
+ client_certificate_requested = true,
+ session = #session{master_secret = MasterSecret,
+ own_certificates = OwnCerts}} = State, Connection) ->
+
+ case ssl_handshake:client_certificate_verify(OwnCerts, MasterSecret,
+ ssl:tls_version(Version), HashSign, PrivateKey, Hist) of
+ #certificate_verify{} = Verified ->
+ Connection:queue_handshake(Verified, State);
+ ignore ->
+ State;
+ #alert{} = Alert ->
+ throw(Alert)
+ end;
+verify_client_cert(#state{client_certificate_requested = false} = State, _) ->
+ State.
+
+client_certify_and_key_exchange(#state{connection_env = #connection_env{negotiated_version = Version}} =
+ State0, Connection) ->
+ try do_client_certify_and_key_exchange(State0, Connection) of
+ State1 = #state{} ->
+ {State2, Actions} = finalize_handshake(State1, certify, Connection),
+ State = State2#state{
+ %% Reinitialize
+ client_certificate_requested = false},
+ Connection:next_event(cipher, no_record, State, Actions)
+ catch
+ throw:#alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, certify, State0)
+ end.
+
+do_client_certify_and_key_exchange(State0, Connection) ->
+ State1 = certify_client(State0, Connection),
+ State2 = key_exchange(State1, Connection),
+ verify_client_cert(State2, Connection).
+
+server_certify_and_key_exchange(State0, Connection) ->
+ State1 = certify_server(State0, Connection),
+ State2 = key_exchange(State1, Connection),
+ request_client_cert(State2, Connection).
+
+certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS},
+ #state{connection_env = #connection_env{private_key = Key},
+ handshake_env = #handshake_env{client_hello_version = {Major, Minor} = Version}}
+ = State, Connection) ->
+ FakeSecret = make_premaster_secret(Version, rsa),
+ %% Countermeasure for Bleichenbacher attack always provide some kind of premaster secret
+ %% and fail handshake later.RFC 5246 section 7.4.7.1.
+ PremasterSecret =
+ try ssl_handshake:premaster_secret(EncPMS, Key) of
+ Secret when erlang:byte_size(Secret) == ?NUM_OF_PREMASTERSECRET_BYTES ->
+ case Secret of
+ <<?BYTE(Major), ?BYTE(Minor), Rest/binary>> -> %% Correct
+ <<?BYTE(Major), ?BYTE(Minor), Rest/binary>>;
+ <<?BYTE(_), ?BYTE(_), Rest/binary>> -> %% Version mismatch
+ <<?BYTE(Major), ?BYTE(Minor), Rest/binary>>
+ end;
+ _ -> %% erlang:byte_size(Secret) =/= ?NUM_OF_PREMASTERSECRET_BYTES
+ FakeSecret
+ catch
+ #alert{description = ?DECRYPT_ERROR} ->
+ FakeSecret
+ end,
+ calculate_master_secret(PremasterSecret, State, Connection, certify, cipher);
+certify_client_key_exchange(#client_diffie_hellman_public{dh_public = ClientPublicDhKey},
+ #state{handshake_env = #handshake_env{diffie_hellman_params = #'DHParameter'{} = Params,
+ kex_keys = {_, ServerDhPrivateKey}}
+ } = State,
+ Connection) ->
+ PremasterSecret = ssl_handshake:premaster_secret(ClientPublicDhKey, ServerDhPrivateKey, Params),
+ calculate_master_secret(PremasterSecret, State, Connection, certify, cipher);
+
+certify_client_key_exchange(#client_ec_diffie_hellman_public{dh_public = ClientPublicEcDhPoint},
+ #state{handshake_env = #handshake_env{kex_keys = ECDHKey}} = State, Connection) ->
+ PremasterSecret = ssl_handshake:premaster_secret(#'ECPoint'{point = ClientPublicEcDhPoint}, ECDHKey),
+ calculate_master_secret(PremasterSecret, State, Connection, certify, cipher);
+certify_client_key_exchange(#client_psk_identity{} = ClientKey,
+ #state{ssl_options =
+ #{user_lookup_fun := PSKLookup}} = State0,
+ Connection) ->
+ PremasterSecret = ssl_handshake:premaster_secret(ClientKey, PSKLookup),
+ calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher);
+certify_client_key_exchange(#client_dhe_psk_identity{} = ClientKey,
+ #state{handshake_env = #handshake_env{diffie_hellman_params = #'DHParameter'{} = Params,
+ kex_keys = {_, ServerDhPrivateKey}},
+ ssl_options =
+ #{user_lookup_fun := PSKLookup}} = State0,
+ Connection) ->
+ PremasterSecret =
+ ssl_handshake:premaster_secret(ClientKey, ServerDhPrivateKey, Params, PSKLookup),
+ calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher);
+certify_client_key_exchange(#client_ecdhe_psk_identity{} = ClientKey,
+ #state{handshake_env = #handshake_env{kex_keys = ServerEcDhPrivateKey},
+ ssl_options =
+ #{user_lookup_fun := PSKLookup}} = State,
+ Connection) ->
+ PremasterSecret =
+ ssl_handshake:premaster_secret(ClientKey, ServerEcDhPrivateKey, PSKLookup),
+ calculate_master_secret(PremasterSecret, State, Connection, certify, cipher);
+certify_client_key_exchange(#client_rsa_psk_identity{} = ClientKey,
+ #state{connection_env = #connection_env{private_key = Key},
+ ssl_options =
+ #{user_lookup_fun := PSKLookup}} = State0,
+ Connection) ->
+ PremasterSecret = ssl_handshake:premaster_secret(ClientKey, Key, PSKLookup),
+ calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher);
+certify_client_key_exchange(#client_srp_public{} = ClientKey,
+ #state{handshake_env = #handshake_env{srp_params = Params,
+ kex_keys = Key}
+ } = State0, Connection) ->
+ PremasterSecret = ssl_handshake:premaster_secret(ClientKey, Key, Params),
+ calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher).
+
+certify_server(#state{handshake_env = #handshake_env{kex_algorithm = KexAlg}} =
+ State, _) when KexAlg == dh_anon;
+ KexAlg == ecdh_anon;
+ KexAlg == psk;
+ KexAlg == dhe_psk;
+ KexAlg == ecdhe_psk;
+ KexAlg == srp_anon ->
+ State;
+certify_server(#state{static_env = #static_env{cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef},
+ session = #session{own_certificates = OwnCerts}} = State, Connection) ->
+ case ssl_handshake:certificate(OwnCerts, CertDbHandle, CertDbRef, server) of
+ Cert = #certificate{} ->
+ Connection:queue_handshake(Cert, State);
+ Alert = #alert{} ->
+ throw(Alert)
+ end.
+
+key_exchange(#state{static_env = #static_env{role = server},
+ handshake_env = #handshake_env{kex_algorithm = rsa}} = State,_) ->
+ State;
+key_exchange(#state{static_env = #static_env{role = server},
+ handshake_env = #handshake_env{kex_algorithm = KexAlg,
+ diffie_hellman_params = #'DHParameter'{} = Params,
+ hashsign_algorithm = HashSignAlgo},
+ connection_env = #connection_env{negotiated_version = Version,
+ private_key = PrivateKey},
+ connection_states = ConnectionStates0} = State0, Connection)
+ when KexAlg == dhe_dss;
+ KexAlg == dhe_rsa;
+ KexAlg == dh_anon ->
+ DHKeys = public_key:generate_key(Params),
+ #{security_parameters := SecParams} =
+ ssl_record:pending_connection_state(ConnectionStates0, read),
+ #security_parameters{client_random = ClientRandom,
+ server_random = ServerRandom} = SecParams,
+ Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), {dh, DHKeys, Params,
+ HashSignAlgo, ClientRandom,
+ ServerRandom,
+ PrivateKey}),
+ #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0),
+ State#state{handshake_env = HsEnv#handshake_env{kex_keys = DHKeys}};
+key_exchange(#state{static_env = #static_env{role = server},
+ handshake_env = #handshake_env{kex_algorithm = KexAlg} = HsEnv,
+ connection_env = #connection_env{private_key = #'ECPrivateKey'{parameters = ECCurve} = Key},
+ session = Session} = State, _)
+ when KexAlg == ecdh_ecdsa;
+ KexAlg == ecdh_rsa ->
+ State#state{handshake_env = HsEnv#handshake_env{kex_keys = Key},
+ session = Session#session{ecc = ECCurve}};
+key_exchange(#state{static_env = #static_env{role = server},
+ handshake_env = #handshake_env{kex_algorithm = KexAlg,
+ hashsign_algorithm = HashSignAlgo},
+ connection_env = #connection_env{negotiated_version = Version,
+ private_key = PrivateKey},
+ session = #session{ecc = ECCCurve},
+ connection_states = ConnectionStates0} = State0, Connection)
+ when KexAlg == ecdhe_ecdsa;
+ KexAlg == ecdhe_rsa;
+ KexAlg == ecdh_anon ->
+
+ ECDHKeys = public_key:generate_key(ECCCurve),
+ #{security_parameters := SecParams} =
+ ssl_record:pending_connection_state(ConnectionStates0, read),
+ #security_parameters{client_random = ClientRandom,
+ server_random = ServerRandom} = SecParams,
+ Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version),
+ {ecdh, ECDHKeys,
+ HashSignAlgo, ClientRandom,
+ ServerRandom,
+ PrivateKey}),
+ #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0),
+ State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys}};
+key_exchange(#state{static_env = #static_env{role = server},
+ handshake_env = #handshake_env{kex_algorithm = psk},
+ ssl_options = #{psk_identity := undefined}} = State, _) ->
+ State;
+key_exchange(#state{static_env = #static_env{role = server},
+ ssl_options = #{psk_identity := PskIdentityHint},
+ handshake_env = #handshake_env{kex_algorithm = psk,
+ hashsign_algorithm = HashSignAlgo},
+ connection_env = #connection_env{negotiated_version = Version,
+ private_key = PrivateKey},
+ connection_states = ConnectionStates0} = State0, Connection) ->
+ #{security_parameters := SecParams} =
+ ssl_record:pending_connection_state(ConnectionStates0, read),
+ #security_parameters{client_random = ClientRandom,
+ server_random = ServerRandom} = SecParams,
+ Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version),
+ {psk, PskIdentityHint,
+ HashSignAlgo, ClientRandom,
+ ServerRandom,
+ PrivateKey}),
+ Connection:queue_handshake(Msg, State0);
+key_exchange(#state{static_env = #static_env{role = server},
+ ssl_options = #{psk_identity := PskIdentityHint},
+ handshake_env = #handshake_env{kex_algorithm = dhe_psk,
+ diffie_hellman_params = #'DHParameter'{} = Params,
+ hashsign_algorithm = HashSignAlgo},
+ connection_env = #connection_env{negotiated_version = Version,
+ private_key = PrivateKey},
+ connection_states = ConnectionStates0
+ } = State0, Connection) ->
+ DHKeys = public_key:generate_key(Params),
+ #{security_parameters := SecParams} =
+ ssl_record:pending_connection_state(ConnectionStates0, read),
+ #security_parameters{client_random = ClientRandom,
+ server_random = ServerRandom} = SecParams,
+ Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version),
+ {dhe_psk,
+ PskIdentityHint, DHKeys, Params,
+ HashSignAlgo, ClientRandom,
+ ServerRandom,
+ PrivateKey}),
+ #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0),
+ State#state{handshake_env = HsEnv#handshake_env{kex_keys = DHKeys}};
+key_exchange(#state{static_env = #static_env{role = server},
+ ssl_options = #{psk_identity := PskIdentityHint},
+ handshake_env = #handshake_env{kex_algorithm = ecdhe_psk,
+ hashsign_algorithm = HashSignAlgo},
+ connection_env = #connection_env{negotiated_version = Version,
+ private_key = PrivateKey},
+ session = #session{ecc = ECCCurve},
+ connection_states = ConnectionStates0
+ } = State0, Connection) ->
+ ECDHKeys = public_key:generate_key(ECCCurve),
+ #{security_parameters := SecParams} =
+ ssl_record:pending_connection_state(ConnectionStates0, read),
+ #security_parameters{client_random = ClientRandom,
+ server_random = ServerRandom} = SecParams,
+ Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version),
+ {ecdhe_psk,
+ PskIdentityHint, ECDHKeys,
+ HashSignAlgo, ClientRandom,
+ ServerRandom,
+ PrivateKey}),
+ #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0),
+ State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys}};
+key_exchange(#state{static_env = #static_env{role = server},
+ handshake_env = #handshake_env{kex_algorithm = rsa_psk},
+ ssl_options = #{psk_identity := undefined}} = State, _) ->
+ State;
+key_exchange(#state{static_env = #static_env{role = server},
+ ssl_options = #{psk_identity := PskIdentityHint},
+ handshake_env = #handshake_env{kex_algorithm = rsa_psk,
+ hashsign_algorithm = HashSignAlgo},
+ connection_env = #connection_env{negotiated_version = Version,
+ private_key = PrivateKey},
+ connection_states = ConnectionStates0
+ } = State0, Connection) ->
+ #{security_parameters := SecParams} =
+ ssl_record:pending_connection_state(ConnectionStates0, read),
+ #security_parameters{client_random = ClientRandom,
+ server_random = ServerRandom} = SecParams,
+ Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version),
+ {psk, PskIdentityHint,
+ HashSignAlgo, ClientRandom,
+ ServerRandom,
+ PrivateKey}),
+ Connection:queue_handshake(Msg, State0);
+key_exchange(#state{static_env = #static_env{role = server},
+ ssl_options = #{user_lookup_fun := LookupFun},
+ handshake_env = #handshake_env{kex_algorithm = KexAlg,
+ hashsign_algorithm = HashSignAlgo},
+ connection_env = #connection_env{negotiated_version = Version,
+ private_key = PrivateKey},
+ session = #session{srp_username = Username},
+ connection_states = ConnectionStates0
+ } = State0, Connection)
+ when KexAlg == srp_dss;
+ KexAlg == srp_rsa;
+ KexAlg == srp_anon ->
+ SrpParams = handle_srp_identity(Username, LookupFun),
+ Keys = case generate_srp_server_keys(SrpParams, 0) of
+ Alert = #alert{} ->
+ throw(Alert);
+ Keys0 = {_,_} ->
+ Keys0
+ end,
+ #{security_parameters := SecParams} =
+ ssl_record:pending_connection_state(ConnectionStates0, read),
+ #security_parameters{client_random = ClientRandom,
+ server_random = ServerRandom} = SecParams,
+ Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version),
+ {srp, Keys, SrpParams,
+ HashSignAlgo, ClientRandom,
+ ServerRandom,
+ PrivateKey}),
+ #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0),
+ State#state{handshake_env = HsEnv#handshake_env{srp_params = SrpParams,
+ kex_keys = Keys}};
+key_exchange(#state{static_env = #static_env{role = client},
+ handshake_env = #handshake_env{kex_algorithm = rsa,
+ public_key_info = PublicKeyInfo,
+ premaster_secret = PremasterSecret},
+ connection_env = #connection_env{negotiated_version = Version}
+ } = State0, Connection) ->
+ Msg = rsa_key_exchange(ssl:tls_version(Version), PremasterSecret, PublicKeyInfo),
+ Connection:queue_handshake(Msg, State0);
+key_exchange(#state{static_env = #static_env{role = client},
+ handshake_env = #handshake_env{kex_algorithm = KexAlg,
+ kex_keys = {DhPubKey, _}},
+ connection_env = #connection_env{negotiated_version = Version}
+ } = State0, Connection)
+ when KexAlg == dhe_dss;
+ KexAlg == dhe_rsa;
+ KexAlg == dh_anon ->
+ Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {dh, DhPubKey}),
+ Connection:queue_handshake(Msg, State0);
+
+key_exchange(#state{static_env = #static_env{role = client},
+ handshake_env = #handshake_env{kex_algorithm = KexAlg,
+ kex_keys = #'ECPrivateKey'{parameters = ECCurve} = Key},
+ connection_env = #connection_env{negotiated_version = Version},
+ session = Session
+ } = State0, Connection)
+ when KexAlg == ecdhe_ecdsa;
+ KexAlg == ecdhe_rsa;
+ KexAlg == ecdh_ecdsa;
+ KexAlg == ecdh_rsa;
+ KexAlg == ecdh_anon ->
+ Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {ecdh, Key}),
+ Connection:queue_handshake(Msg, State0#state{session = Session#session{ecc = ECCurve}});
+key_exchange(#state{static_env = #static_env{role = client},
+ handshake_env = #handshake_env{kex_algorithm = psk},
+ connection_env = #connection_env{negotiated_version = Version},
+ ssl_options = #{psk_identity := PSKIdentity}} = State0, Connection) ->
+ Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version),
+ {psk, PSKIdentity}),
+ Connection:queue_handshake(Msg, State0);
+key_exchange(#state{static_env = #static_env{role = client},
+ handshake_env = #handshake_env{kex_algorithm = dhe_psk,
+ kex_keys = {DhPubKey, _}},
+ connection_env = #connection_env{negotiated_version = Version},
+ ssl_options = #{psk_identity := PSKIdentity}} = State0, Connection) ->
+ Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version),
+ {dhe_psk,
+ PSKIdentity, DhPubKey}),
+ Connection:queue_handshake(Msg, State0);
+
+key_exchange(#state{static_env = #static_env{role = client},
+ handshake_env = #handshake_env{kex_algorithm = ecdhe_psk,
+ kex_keys = ECDHKeys},
+ connection_env = #connection_env{negotiated_version = Version},
+ ssl_options = #{psk_identity := PSKIdentity}} = State0, Connection) ->
+ Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version),
+ {ecdhe_psk,
+ PSKIdentity, ECDHKeys}),
+ Connection:queue_handshake(Msg, State0);
+
+key_exchange(#state{static_env = #static_env{role = client},
+ handshake_env = #handshake_env{kex_algorithm = rsa_psk,
+ public_key_info = PublicKeyInfo,
+ premaster_secret = PremasterSecret},
+ connection_env = #connection_env{negotiated_version = Version},
+ ssl_options = #{psk_identity := PSKIdentity}}
+ = State0, Connection) ->
+ Msg = rsa_psk_key_exchange(ssl:tls_version(Version), PSKIdentity,
+ PremasterSecret, PublicKeyInfo),
+ Connection:queue_handshake(Msg, State0);
+key_exchange(#state{static_env = #static_env{role = client},
+ handshake_env = #handshake_env{kex_algorithm = KexAlg,
+ kex_keys = {ClientPubKey, _}},
+ connection_env = #connection_env{negotiated_version = Version}}
+ = State0, Connection)
+ when KexAlg == srp_dss;
+ KexAlg == srp_rsa;
+ KexAlg == srp_anon ->
+ Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {srp, ClientPubKey}),
+ Connection:queue_handshake(Msg, State0).
+
+rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo = {Algorithm, _, _})
+ when Algorithm == ?rsaEncryption;
+ Algorithm == ?md2WithRSAEncryption;
+ Algorithm == ?md5WithRSAEncryption;
+ Algorithm == ?sha1WithRSAEncryption;
+ Algorithm == ?sha224WithRSAEncryption;
+ Algorithm == ?sha256WithRSAEncryption;
+ Algorithm == ?sha384WithRSAEncryption;
+ Algorithm == ?sha512WithRSAEncryption
+ ->
+ ssl_handshake:key_exchange(client, ssl:tls_version(Version),
+ {premaster_secret, PremasterSecret,
+ PublicKeyInfo});
+rsa_key_exchange(_, _, _) ->
+ throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, pub_key_is_not_rsa)).
+
+rsa_psk_key_exchange(Version, PskIdentity, PremasterSecret,
+ PublicKeyInfo = {Algorithm, _, _})
+ when Algorithm == ?rsaEncryption;
+ Algorithm == ?md2WithRSAEncryption;
+ Algorithm == ?md5WithRSAEncryption;
+ Algorithm == ?sha1WithRSAEncryption;
+ Algorithm == ?sha224WithRSAEncryption;
+ Algorithm == ?sha256WithRSAEncryption;
+ Algorithm == ?sha384WithRSAEncryption;
+ Algorithm == ?sha512WithRSAEncryption
+ ->
+ ssl_handshake:key_exchange(client, ssl:tls_version(Version),
+ {psk_premaster_secret, PskIdentity, PremasterSecret,
+ PublicKeyInfo});
+rsa_psk_key_exchange(_, _, _, _) ->
+ throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, pub_key_is_not_rsa)).
+
+request_client_cert(#state{handshake_env = #handshake_env{kex_algorithm = Alg}} = State, _)
+ when Alg == dh_anon;
+ Alg == ecdh_anon;
+ Alg == psk;
+ Alg == dhe_psk;
+ Alg == ecdhe_psk;
+ Alg == rsa_psk;
+ Alg == srp_dss;
+ Alg == srp_rsa;
+ Alg == srp_anon ->
+ State;
+
+request_client_cert(#state{static_env = #static_env{cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef},
+ connection_env = #connection_env{negotiated_version = Version},
+ ssl_options = #{verify := verify_peer,
+ signature_algs := SupportedHashSigns},
+ connection_states = ConnectionStates0} = State0, Connection) ->
+ #{security_parameters :=
+ #security_parameters{cipher_suite = CipherSuite}} =
+ ssl_record:pending_connection_state(ConnectionStates0, read),
+ TLSVersion = ssl:tls_version(Version),
+ HashSigns = ssl_handshake:available_signature_algs(SupportedHashSigns,
+ TLSVersion),
+ Msg = ssl_handshake:certificate_request(CipherSuite, CertDbHandle, CertDbRef,
+ HashSigns, TLSVersion),
+ State = Connection:queue_handshake(Msg, State0),
+ State#state{client_certificate_requested = true};
+
+request_client_cert(#state{ssl_options = #{verify := verify_none}} =
+ State, _) ->
+ State.
+
+calculate_master_secret(PremasterSecret,
+ #state{connection_env = #connection_env{negotiated_version = Version},
+ connection_states = ConnectionStates0,
+ session = Session0} = State0, Connection,
+ _Current, Next) ->
+ case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret,
+ ConnectionStates0, server) of
+ {MasterSecret, ConnectionStates} ->
+ Session = Session0#session{master_secret = MasterSecret},
+ State = State0#state{connection_states = ConnectionStates,
+ session = Session},
+ Connection:next_event(Next, no_record, State);
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, certify, State0)
+ end.
+
+finalize_handshake(State0, StateName, Connection) ->
+ #state{connection_states = ConnectionStates0} =
+ State1 = cipher_protocol(State0, Connection),
+
+ ConnectionStates =
+ ssl_record:activate_pending_connection_state(ConnectionStates0,
+ write, Connection),
+
+ State2 = State1#state{connection_states = ConnectionStates},
+ State = next_protocol(State2, Connection),
+ finished(State, StateName, Connection).
+
+next_protocol(#state{static_env = #static_env{role = server}} = State, _) ->
+ State;
+next_protocol(#state{handshake_env = #handshake_env{negotiated_protocol = undefined}} = State, _) ->
+ State;
+next_protocol(#state{handshake_env = #handshake_env{expecting_next_protocol_negotiation = false}} = State, _) ->
+ State;
+next_protocol(#state{handshake_env = #handshake_env{negotiated_protocol = NextProtocol}} = State0, Connection) ->
+ NextProtocolMessage = ssl_handshake:next_protocol(NextProtocol),
+ Connection:queue_handshake(NextProtocolMessage, State0).
+
+cipher_protocol(State, Connection) ->
+ Connection:queue_change_cipher(#change_cipher_spec{}, State).
+
+finished(#state{static_env = #static_env{role = Role},
+ handshake_env = #handshake_env{tls_handshake_history = Hist},
+ connection_env = #connection_env{negotiated_version = Version},
+ session = Session,
+ connection_states = ConnectionStates0} = State0,
+ StateName, Connection) ->
+ MasterSecret = Session#session.master_secret,
+ Finished = ssl_handshake:finished(ssl:tls_version(Version), Role,
+ get_current_prf(ConnectionStates0, write),
+ MasterSecret, Hist),
+ ConnectionStates = save_verify_data(Role, Finished, ConnectionStates0, StateName),
+ Connection:send_handshake(Finished, State0#state{connection_states =
+ ConnectionStates}).
+
+save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, certify) ->
+ ssl_record:set_client_verify_data(current_write, Data, ConnectionStates);
+save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, cipher) ->
+ ssl_record:set_server_verify_data(current_both, Data, ConnectionStates);
+save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, abbreviated) ->
+ ssl_record:set_client_verify_data(current_both, Data, ConnectionStates);
+save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, abbreviated) ->
+ ssl_record:set_server_verify_data(current_write, Data, ConnectionStates).
+
+calculate_secret(#server_dh_params{dh_p = Prime, dh_g = Base,
+ dh_y = ServerPublicDhKey} = Params,
+ #state{handshake_env = HsEnv} = State, Connection) ->
+ Keys = {_, PrivateDhKey} = crypto:generate_key(dh, [Prime, Base]),
+ PremasterSecret =
+ ssl_handshake:premaster_secret(ServerPublicDhKey, PrivateDhKey, Params),
+ calculate_master_secret(PremasterSecret,
+ State#state{handshake_env = HsEnv#handshake_env{kex_keys = Keys}},
+ Connection, certify, certify);
+
+calculate_secret(#server_ecdh_params{curve = ECCurve, public = ECServerPubKey},
+ #state{handshake_env = HsEnv,
+ session = Session} = State, Connection) ->
+ ECDHKeys = public_key:generate_key(ECCurve),
+ PremasterSecret =
+ ssl_handshake:premaster_secret(#'ECPoint'{point = ECServerPubKey}, ECDHKeys),
+ calculate_master_secret(PremasterSecret,
+ State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys},
+ session = Session#session{ecc = ECCurve}},
+ Connection, certify, certify);
+
+calculate_secret(#server_psk_params{
+ hint = IdentityHint},
+ #state{handshake_env = HsEnv} = State, Connection) ->
+ %% store for later use
+ Connection:next_event(certify, no_record,
+ State#state{handshake_env =
+ HsEnv#handshake_env{server_psk_identity = IdentityHint}});
+
+calculate_secret(#server_dhe_psk_params{
+ dh_params = #server_dh_params{dh_p = Prime, dh_g = Base}} = ServerKey,
+ #state{handshake_env = HsEnv,
+ ssl_options = #{user_lookup_fun := PSKLookup}} =
+ State, Connection) ->
+ Keys = {_, PrivateDhKey} =
+ crypto:generate_key(dh, [Prime, Base]),
+ PremasterSecret = ssl_handshake:premaster_secret(ServerKey, PrivateDhKey, PSKLookup),
+ calculate_master_secret(PremasterSecret, State#state{handshake_env = HsEnv#handshake_env{kex_keys = Keys}},
+ Connection, certify, certify);
+
+calculate_secret(#server_ecdhe_psk_params{
+ dh_params = #server_ecdh_params{curve = ECCurve}} = ServerKey,
+ #state{ssl_options = #{user_lookup_fun := PSKLookup}} =
+ #state{handshake_env = HsEnv,
+ session = Session} = State, Connection) ->
+ ECDHKeys = public_key:generate_key(ECCurve),
+
+ PremasterSecret = ssl_handshake:premaster_secret(ServerKey, ECDHKeys, PSKLookup),
+ calculate_master_secret(PremasterSecret,
+ State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys},
+ session = Session#session{ecc = ECCurve}},
+ Connection, certify, certify);
+
+calculate_secret(#server_srp_params{srp_n = Prime, srp_g = Generator} = ServerKey,
+ #state{handshake_env = HsEnv,
+ ssl_options = #{srp_identity := SRPId}} = State,
+ Connection) ->
+ Keys = generate_srp_client_keys(Generator, Prime, 0),
+ PremasterSecret = ssl_handshake:premaster_secret(ServerKey, Keys, SRPId),
+ calculate_master_secret(PremasterSecret, State#state{handshake_env = HsEnv#handshake_env{kex_keys = Keys}}, Connection,
+ certify, certify).
+
+master_secret(#alert{} = Alert, _) ->
+ Alert;
+master_secret(PremasterSecret, #state{static_env = #static_env{role = Role},
+ connection_env = #connection_env{negotiated_version = Version},
+ session = Session,
+ connection_states = ConnectionStates0} = State) ->
+ case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret,
+ ConnectionStates0, Role) of
+ {MasterSecret, ConnectionStates} ->
+ State#state{
+ session =
+ Session#session{master_secret = MasterSecret},
+ connection_states = ConnectionStates};
+ #alert{} = Alert ->
+ Alert
+ end.
+
+generate_srp_server_keys(_SrpParams, 10) ->
+ ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
+generate_srp_server_keys(SrpParams =
+ #srp_user{generator = Generator, prime = Prime,
+ verifier = Verifier}, N) ->
+ try crypto:generate_key(srp, {host, [Verifier, Generator, Prime, '6a']}) of
+ Keys ->
+ Keys
+ catch
+ error:_ ->
+ generate_srp_server_keys(SrpParams, N+1)
+ end.
+
+generate_srp_client_keys(_Generator, _Prime, 10) ->
+ ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
+generate_srp_client_keys(Generator, Prime, N) ->
+
+ try crypto:generate_key(srp, {user, [Generator, Prime, '6a']}) of
+ Keys ->
+ Keys
+ catch
+ error:_ ->
+ generate_srp_client_keys(Generator, Prime, N+1)
+ end.
+
+handle_srp_identity(Username, {Fun, UserState}) ->
+ case Fun(srp, Username, UserState) of
+ {ok, {SRPParams, Salt, DerivedKey}}
+ when is_atom(SRPParams), is_binary(Salt), is_binary(DerivedKey) ->
+ {Generator, Prime} = ssl_srp_primes:get_srp_params(SRPParams),
+ Verifier = crypto:mod_pow(Generator, DerivedKey, Prime),
+ #srp_user{generator = Generator, prime = Prime,
+ salt = Salt, verifier = Verifier};
+ #alert{} = Alert ->
+ throw(Alert);
+ _ ->
+ throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER))
+ end.
+
+
+cipher_role(client, Data, Session, #state{static_env = #static_env{protocol_cb = Connection},
+ connection_states = ConnectionStates0} = State0) ->
+ ConnectionStates = ssl_record:set_server_verify_data(current_both, Data,
+ ConnectionStates0),
+ {Record, State} = ssl_gen_statem:prepare_connection(State0#state{session = Session,
+ connection_states = ConnectionStates},
+ Connection),
+ Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close}]);
+cipher_role(server, Data, Session, #state{static_env = #static_env{protocol_cb = Connection},
+ connection_states = ConnectionStates0} = State0) ->
+ ConnectionStates1 = ssl_record:set_client_verify_data(current_read, Data,
+ ConnectionStates0),
+ {State1, Actions} =
+ finalize_handshake(State0#state{connection_states = ConnectionStates1,
+ session = Session}, cipher, Connection),
+ {Record, State} = ssl_gen_statem:prepare_connection(State1, Connection),
+ Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close} | Actions]).
+
+is_anonymous(KexAlg) when KexAlg == dh_anon;
+ KexAlg == ecdh_anon;
+ KexAlg == psk;
+ KexAlg == dhe_psk;
+ KexAlg == ecdhe_psk;
+ KexAlg == rsa_psk;
+ KexAlg == srp_anon ->
+ true;
+is_anonymous(_) ->
+ false.
+
+get_current_prf(CStates, Direction) ->
+ #{security_parameters := SecParams} = ssl_record:current_connection_state(CStates, Direction),
+ SecParams#security_parameters.prf_algorithm.
+get_pending_prf(CStates, Direction) ->
+ #{security_parameters := SecParams} = ssl_record:pending_connection_state(CStates, Direction),
+ SecParams#security_parameters.prf_algorithm.
+
+opposite_role(client) ->
+ server;
+opposite_role(server) ->
+ client.
+
+
+
+session_handle_params(#server_ecdh_params{curve = ECCurve}, Session) ->
+ Session#session{ecc = ECCurve};
+session_handle_params(_, Session) ->
+ Session.
+
+handle_session(server, #{reuse_sessions := true},
+ _Host, _Port, Trackers, #session{is_resumable = false} = Session) ->
+ Tracker = proplists:get_value(session_id_tracker, Trackers),
+ server_register_session(Tracker, Session#session{is_resumable = true});
+handle_session(Role = client, #{verify := verify_peer,
+ reuse_sessions := Reuse} = SslOpts,
+ Host, Port, _, #session{is_resumable = false} = Session) when Reuse =/= false ->
+ client_register_session(host_id(Role, Host, SslOpts), Port, Session#session{is_resumable = true},
+ reg_type(Reuse));
+handle_session(_,_,_,_,_, Session) ->
+ Session.
+
+reg_type(save) ->
+ true;
+reg_type(true) ->
+ unique.
+
+client_register_session(Host, Port, Session, Save) ->
+ ssl_manager:register_session(Host, Port, Session, Save),
+ Session.
+server_register_session(Tracker, Session) ->
+ ssl_server_session_cache:register_session(Tracker, Session),
+ Session.
+
+host_id(client, _Host, #{server_name_indication := Hostname}) when is_list(Hostname) ->
+ Hostname;
+host_id(_, Host, _) ->
+ Host.
+
+handle_new_session(NewId, CipherSuite, Compression,
+ #state{static_env = #static_env{protocol_cb = Connection},
+ session = Session0
+ } = State0) ->
+ Session = Session0#session{session_id = NewId,
+ cipher_suite = CipherSuite,
+ compression_method = Compression},
+ Connection:next_event(certify, no_record, State0#state{session = Session}).
+
+handle_resumed_session(SessId, #state{static_env = #static_env{host = Host,
+ port = Port,
+ protocol_cb = Connection,
+ session_cache = Cache,
+ session_cache_cb = CacheCb},
+ connection_env = #connection_env{negotiated_version = Version},
+ connection_states = ConnectionStates0,
+ ssl_options = Opts} = State) ->
+
+ Session = case maps:get(reuse_session, Opts, undefined) of
+ {SessId,SessionData} when is_binary(SessId), is_binary(SessionData) ->
+ binary_to_term(SessionData, [safe]);
+ _Else ->
+ CacheCb:lookup(Cache, {{Host, Port}, SessId})
+ end,
+
+ case ssl_handshake:master_secret(ssl:tls_version(Version), Session,
+ ConnectionStates0, client) of
+ {_, ConnectionStates} ->
+ Connection:next_event(abbreviated, no_record, State#state{
+ connection_states = ConnectionStates,
+ session = Session});
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, hello, State)
+ end.
+
+make_premaster_secret({MajVer, MinVer}, rsa) ->
+ Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2),
+ <<?BYTE(MajVer), ?BYTE(MinVer), Rand/binary>>;
+make_premaster_secret(_, _) ->
+ undefined.
+
+negotiated_hashsign(undefined, KexAlg, PubKeyInfo, Version) ->
+ %% Not negotiated choose default
+ case is_anonymous(KexAlg) of
+ true ->
+ {null, anon};
+ false ->
+ {PubAlg, _, _} = PubKeyInfo,
+ ssl_handshake:select_hashsign_algs(undefined, PubAlg, Version)
+ end;
+negotiated_hashsign(HashSign = {_, _}, _, _, _) ->
+ HashSign.
+
+%% Handle SNI extension in pre-TLS 1.3 and DTLS
+handle_sni_extension(#state{static_env =
+ #static_env{protocol_cb = Connection}} = State0,
+ Hello) ->
+ PossibleSNI = Connection:select_sni_extension(Hello),
+ case ssl_gen_statem:handle_sni_extension(PossibleSNI, State0) of
+ {ok, State} ->
+ State;
+ {error, Alert} ->
+ Alert
+ end.
+
+ensure_tls({254, _} = Version) ->
+ dtls_v1:corresponding_tls_version(Version);
+ensure_tls(Version) ->
+ Version.
+
+ocsp_info(#{ocsp_expect := stapled,
+ ocsp_response := CertStatus} = OcspState,
+ #{ocsp_responder_certs := OcspResponderCerts}, PeerCert) ->
+ #{cert_ext => #{public_key:pkix_subject_id(PeerCert) => [CertStatus]},
+ ocsp_responder_certs => OcspResponderCerts,
+ ocsp_state => OcspState
+ };
+ocsp_info(#{ocsp_expect := no_staple} = OcspState, _, PeerCert) ->
+ #{cert_ext => #{public_key:pkix_subject_id(PeerCert) => []},
+ ocsp_responder_certs => [],
+ ocsp_state => OcspState
+ }.
diff --git a/lib/ssl/src/tls_gen_connection.erl b/lib/ssl/src/tls_gen_connection.erl
new file mode 100644
index 0000000000..7c16bfa3de
--- /dev/null
+++ b/lib/ssl/src/tls_gen_connection.erl
@@ -0,0 +1,767 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2020-2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+%%----------------------------------------------------------------------
+%% Purpose:
+%%----------------------------------------------------------------------
+
+-module(tls_gen_connection).
+
+-include_lib("public_key/include/public_key.hrl").
+-include_lib("kernel/include/logger.hrl").
+
+-include("tls_connection.hrl").
+-include("tls_handshake.hrl").
+-include("tls_record.hrl").
+-include("ssl_alert.hrl").
+-include("ssl_api.hrl").
+-include("ssl_internal.hrl").
+
+%% Setup
+-export([start_fsm/8,
+ pids/1,
+ initialize_tls_sender/1]).
+
+%% Handshake handling
+-export([renegotiation/2,
+ renegotiate/2,
+ send_handshake/2,
+ send_handshake_flight/1,
+ queue_handshake/2,
+ queue_change_cipher/2,
+ reinit/1,
+ reinit_handshake_data/1,
+ select_sni_extension/1,
+ empty_connection_state/2,
+ encode_handshake/4]).
+
+%% State transition handling
+-export([next_event/3,
+ next_event/4,
+ handle_protocol_record/3]).
+
+%% Data handling
+-export([socket/4,
+ setopts/3,
+ getopts/3,
+ handle_info/3]).
+
+%% Alert and close handling
+-export([send_alert/2,
+ send_alert_in_connection/2,
+ send_sync_alert/2,
+ close/5,
+ protocol_name/0]).
+
+-define(DIST_CNTRL_SPAWN_OPTS, [{priority, max}]).
+
+%%====================================================================
+%% Internal application API
+%%====================================================================
+%%====================================================================
+%% Setup
+%%====================================================================
+start_fsm(Role, Host, Port, Socket, {#{erl_dist := false},_, Trackers} = Opts,
+ User, {CbModule, _,_, _, _} = CbInfo,
+ Timeout) ->
+ try
+ {ok, Sender} = tls_sender:start(),
+ {ok, Pid} = tls_connection_sup:start_child([Role, Sender, Host, Port, Socket,
+ Opts, User, CbInfo]),
+ {ok, SslSocket} = ssl_gen_statem:socket_control(?MODULE, Socket, [Pid, Sender], CbModule, Trackers),
+ ssl_gen_statem:handshake(SslSocket, Timeout)
+ catch
+ error:{badmatch, {error, _} = Error} ->
+ Error
+ end;
+
+start_fsm(Role, Host, Port, Socket, {#{erl_dist := true},_, Trackers} = Opts,
+ User, {CbModule, _,_, _, _} = CbInfo,
+ Timeout) ->
+ try
+ {ok, Sender} = tls_sender:start([{spawn_opt, ?DIST_CNTRL_SPAWN_OPTS}]),
+ {ok, Pid} = tls_connection_sup:start_child_dist([Role, Sender, Host, Port, Socket,
+ Opts, User, CbInfo]),
+ {ok, SslSocket} = ssl_gen_statem:socket_control(?MODULE, Socket, [Pid, Sender], CbModule, Trackers),
+ ssl_gen_statem:handshake(SslSocket, Timeout)
+ catch
+ error:{badmatch, {error, _} = Error} ->
+ Error
+ end.
+
+pids(#state{protocol_specific = #{sender := Sender}}) ->
+ [self(), Sender].
+
+initialize_tls_sender(#state{static_env = #static_env{
+ role = Role,
+ transport_cb = Transport,
+ socket = Socket,
+ trackers = Trackers
+ },
+ connection_env = #connection_env{negotiated_version = Version},
+ socket_options = SockOpts,
+ ssl_options = #{renegotiate_at := RenegotiateAt,
+ key_update_at := KeyUpdateAt,
+ log_level := LogLevel},
+ connection_states = #{current_write := ConnectionWriteState},
+ protocol_specific = #{sender := Sender}}) ->
+ Init = #{current_write => ConnectionWriteState,
+ role => Role,
+ socket => Socket,
+ socket_options => SockOpts,
+ trackers => Trackers,
+ transport_cb => Transport,
+ negotiated_version => Version,
+ renegotiate_at => RenegotiateAt,
+ key_update_at => KeyUpdateAt,
+ log_level => LogLevel},
+ tls_sender:initialize(Sender, Init).
+
+%%====================================================================
+%% Handshake handling
+%%====================================================================
+renegotiation(Pid, WriteState) ->
+ gen_statem:call(Pid, {user_renegotiate, WriteState}).
+
+renegotiate(#state{static_env = #static_env{role = client},
+ handshake_env = HsEnv} = State, Actions) ->
+ %% Handle same way as if server requested
+ %% the renegotiation
+ Hs0 = ssl_handshake:init_handshake_history(),
+ {next_state, connection, State#state{handshake_env = HsEnv#handshake_env{tls_handshake_history = Hs0}},
+ [{next_event, internal, #hello_request{}} | Actions]};
+renegotiate(#state{static_env = #static_env{role = server,
+ socket = Socket,
+ transport_cb = Transport},
+ handshake_env = HsEnv,
+ connection_env = #connection_env{negotiated_version = Version},
+ connection_states = ConnectionStates0} = State0, Actions) ->
+ HelloRequest = ssl_handshake:hello_request(),
+ Frag = tls_handshake:encode_handshake(HelloRequest, Version),
+ Hs0 = ssl_handshake:init_handshake_history(),
+ {BinMsg, ConnectionStates} =
+ tls_record:encode_handshake(Frag, Version, ConnectionStates0),
+ tls_socket:send(Transport, Socket, BinMsg),
+ State = State0#state{connection_states =
+ ConnectionStates,
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = Hs0}},
+ next_event(hello, no_record, State, Actions).
+
+send_handshake(Handshake, State) ->
+ send_handshake_flight(queue_handshake(Handshake, State)).
+
+queue_handshake(Handshake, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv,
+ connection_env = #connection_env{negotiated_version = Version},
+ flight_buffer = Flight0,
+ ssl_options = #{log_level := LogLevel},
+ connection_states = ConnectionStates0} = State0) ->
+ {BinHandshake, ConnectionStates, Hist} =
+ encode_handshake(Handshake, Version, ConnectionStates0, Hist0),
+ ssl_logger:debug(LogLevel, outbound, 'handshake', Handshake),
+ ssl_logger:debug(LogLevel, outbound, 'record', BinHandshake),
+
+ State0#state{connection_states = ConnectionStates,
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist},
+ flight_buffer = Flight0 ++ [BinHandshake]}.
+
+-spec send_handshake_flight(StateIn) -> {StateOut, FlightBuffer} when
+ StateIn :: #state{},
+ StateOut :: #state{},
+ FlightBuffer :: list().
+send_handshake_flight(#state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
+ flight_buffer = Flight} = State0) ->
+ tls_socket:send(Transport, Socket, Flight),
+ {State0#state{flight_buffer = []}, []}.
+
+
+queue_change_cipher(Msg, #state{connection_env = #connection_env{negotiated_version = Version},
+ flight_buffer = Flight0,
+ ssl_options = #{log_level := LogLevel},
+ connection_states = ConnectionStates0} = State0) ->
+ {BinChangeCipher, ConnectionStates} =
+ encode_change_cipher(Msg, Version, ConnectionStates0),
+ ssl_logger:debug(LogLevel, outbound, 'record', BinChangeCipher),
+ State0#state{connection_states = ConnectionStates,
+ flight_buffer = Flight0 ++ [BinChangeCipher]}.
+
+reinit(#state{protocol_specific = #{sender := Sender},
+ connection_env = #connection_env{negotiated_version = Version},
+ connection_states = #{current_write := Write}} = State) ->
+ tls_sender:update_connection_state(Sender, Write, Version),
+ reinit_handshake_data(State).
+
+reinit_handshake_data(#state{handshake_env = HsEnv} =State) ->
+ %% premaster_secret, public_key_info and tls_handshake_info
+ %% are only needed during the handshake phase.
+ %% To reduce memory foot print of a connection reinitialize them.
+ State#state{
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = ssl_handshake:init_handshake_history(),
+ public_key_info = undefined,
+ premaster_secret = undefined}
+ }.
+
+select_sni_extension(#client_hello{extensions = #{sni := SNI}}) ->
+ SNI;
+select_sni_extension(_) ->
+ undefined.
+
+empty_connection_state(ConnectionEnd, BeastMitigation) ->
+ ssl_record:empty_connection_state(ConnectionEnd, BeastMitigation).
+
+%%====================================================================
+%% Data handling
+%%====================================================================
+
+socket(Pids, Transport, Socket, Trackers) ->
+ tls_socket:socket(Pids, Transport, Socket, tls_connection, Trackers).
+
+setopts(Transport, Socket, Other) ->
+ tls_socket:setopts(Transport, Socket, Other).
+
+getopts(Transport, Socket, Tag) ->
+ tls_socket:getopts(Transport, Socket, Tag).
+
+%% raw data from socket, upack records
+handle_info({Protocol, _, Data}, StateName,
+ #state{static_env = #static_env{data_tag = Protocol},
+ connection_env = #connection_env{negotiated_version = Version}} = State0) ->
+ case next_tls_record(Data, StateName, State0) of
+ {Record, State} ->
+ next_event(StateName, Record, State);
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, StateName, State0)
+ end;
+handle_info({PassiveTag, Socket}, StateName,
+ #state{static_env = #static_env{socket = Socket,
+ passive_tag = PassiveTag},
+ start_or_recv_from = From,
+ protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs},
+ protocol_specific = PS
+ } = State0) ->
+ case (From =/= undefined) andalso (CTs == []) of
+ true ->
+ {Record, State} = activate_socket(State0#state{protocol_specific = PS#{active_n_toggle => true}}),
+ next_event(StateName, Record, State);
+ false ->
+ next_event(StateName, no_record,
+ State0#state{protocol_specific = PS#{active_n_toggle => true}})
+ end;
+handle_info({CloseTag, Socket}, StateName,
+ #state{static_env = #static_env{
+ role = Role,
+ host = Host,
+ port = Port,
+ socket = Socket,
+ close_tag = CloseTag},
+ handshake_env = #handshake_env{renegotiation = Type},
+ session = Session} = State) when StateName =/= connection ->
+ ssl_gen_statem:maybe_invalidate_session(Type, Role, Host, Port, Session),
+ Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, transport_closed),
+ ssl_gen_statem:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State),
+ {stop, {shutdown, transport_closed}, State};
+handle_info({CloseTag, Socket}, StateName,
+ #state{static_env = #static_env{
+ role = Role,
+ socket = Socket,
+ close_tag = CloseTag},
+ start_or_recv_from = From,
+ socket_options = #socket_options{active = Active},
+ protocol_specific = PS} = State) ->
+
+ %% Note that as of TLS 1.1,
+ %% failure to properly close a connection no longer requires that a
+ %% session not be resumed. This is a change from TLS 1.0 to conform
+ %% with widespread implementation practice.
+
+ case (Active == false) andalso (From == undefined) of
+ false ->
+ %% As invalidate_sessions here causes performance issues,
+ %% we will conform to the widespread implementation
+ %% practice and go aginst the spec
+ %% case Version of
+ %% {3, N} when N >= 1 ->
+ %% ok;
+ %% _ ->
+ %% invalidate_session(Role, Host, Port, Session)
+ %% ok
+ %% end,
+ Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, transport_closed),
+ ssl_gen_statem:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State),
+ {stop, {shutdown, transport_closed}, State};
+ true ->
+ %% Wait for next socket operation (most probably
+ %% ssl:setopts(S, [{active, true | once | N}]) or
+ %% ssl:recv(S, N, Timeout) before closing. Possible
+ %% buffered data will be deliverd by the code handling
+ %% these options before closing. In the case of the
+ %% peer resetting the connection hard, that is
+ %% we do not receive any close ALERT, and an active once (or possible N)
+ %% strategy is used by the client we want to later trigger a new
+ %% "transport closed" message. This is achieved by setting the internal
+ %% active_n_toggle here which will cause
+ %% this to happen when tls_connection:activate_socket/1
+ %% is called after all data has been deliver.
+ {next_state, StateName, State#state{protocol_specific = PS#{active_n_toggle => true}}, []}
+ end;
+handle_info({'EXIT', Sender, Reason}, _,
+ #state{protocol_specific = #{sender := Sender}} = State) ->
+ {stop, {shutdown, {sender_died, Reason}}, State};
+handle_info(Msg, StateName, State) ->
+ ssl_gen_statem:handle_info(Msg, StateName, State).
+
+%%====================================================================
+%% State transition handling
+%%====================================================================
+next_event(StateName, Record, State) ->
+ next_event(StateName, Record, State, []).
+
+next_event(StateName, no_record, #state{static_env = #static_env{role = Role}} = State0, Actions) ->
+ case next_record(StateName, State0) of
+ {no_record, State} ->
+ ssl_gen_statem:hibernate_after(StateName, State, Actions);
+ {Record, State} ->
+ next_event(StateName, Record, State, Actions);
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State0),
+ {stop, {shutdown, own_alert}, State0}
+ end;
+next_event(StateName, #ssl_tls{} = Record, State, Actions) ->
+ {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]};
+next_event(StateName, #alert{} = Alert, State, Actions) ->
+ {next_state, StateName, State, [{next_event, internal, Alert} | Actions]}.
+
+%%% TLS record protocol level application data messages
+handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName,
+ #state{start_or_recv_from = From,
+ socket_options = #socket_options{active = false}} = State0) when From =/= undefined ->
+ case ssl_gen_statem:read_application_data(Data, State0) of
+ {stop, _, _} = Stop->
+ Stop;
+ {Record, #state{start_or_recv_from = Caller} = State} ->
+ TimerAction = case Caller of
+ undefined -> %% Passive recv complete cancel timer
+ [{{timeout, recv}, infinity, timeout}];
+ _ ->
+ []
+ end,
+ next_event(StateName, Record, State, TimerAction)
+ end;
+handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName, State0) ->
+ case ssl_gen_statem:read_application_data(Data, State0) of
+ {stop, _, _} = Stop->
+ Stop;
+ {Record, State} ->
+ next_event(StateName, Record, State)
+ end;
+%%% TLS record protocol level handshake messages
+handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, fragment = Data},
+ StateName, #state{protocol_buffers =
+ #protocol_buffers{tls_handshake_buffer = Buf0} = Buffers,
+ connection_env = #connection_env{negotiated_version = Version},
+ static_env = #static_env{role = Role},
+ ssl_options = Options} = State0) ->
+ try
+ %% Calculate the effective version that should be used when decoding an incoming handshake
+ %% message.
+ EffectiveVersion = effective_version(Version, Options, Role),
+ {Packets, Buf} = tls_handshake:get_tls_handshake(EffectiveVersion,Data,Buf0, Options),
+ State =
+ State0#state{protocol_buffers =
+ Buffers#protocol_buffers{tls_handshake_buffer = Buf}},
+ case Packets of
+ [] ->
+ assert_buffer_sanity(Buf, Options),
+ next_event(StateName, no_record, State);
+ _ ->
+ Events = tls_handshake_events(Packets),
+ case StateName of
+ connection ->
+ ssl_gen_statem:hibernate_after(StateName, State, Events);
+ _ ->
+ HsEnv = State#state.handshake_env,
+ {next_state, StateName,
+ State#state{handshake_env =
+ HsEnv#handshake_env{unprocessed_handshake_events
+ = unprocessed_events(Events)}}, Events}
+ end
+ end
+ catch throw:#alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, StateName, State0)
+ end;
+%%% TLS record protocol level change cipher messages
+handle_protocol_record(#ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, StateName, State) ->
+ {next_state, StateName, State, [{next_event, internal, #change_cipher_spec{type = Data}}]};
+%%% TLS record protocol level Alert messages
+handle_protocol_record(#ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName,
+ #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
+ try decode_alerts(EncAlerts) of
+ Alerts = [_|_] ->
+ handle_alerts(Alerts, {next_state, StateName, State});
+ [] ->
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, empty_alert),
+ Version, StateName, State);
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, StateName, State)
+ catch
+ _:_ ->
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, alert_decode_error),
+ Version, StateName, State)
+
+ end;
+%% Ignore unknown TLS record level protocol messages
+handle_protocol_record(#ssl_tls{type = _Unknown}, StateName, State) ->
+ {next_state, StateName, State, []}.
+
+%%====================================================================
+%% Alert and close handling
+%%====================================================================
+
+%%--------------------------------------------------------------------
+-spec encode_alert(#alert{}, ssl_record:ssl_version(), ssl_record:connection_states()) ->
+ {iolist(), ssl_record:connection_states()}.
+%%
+%% Description: Encodes an alert
+%%--------------------------------------------------------------------
+encode_alert(#alert{} = Alert, Version, ConnectionStates) ->
+ tls_record:encode_alert_record(Alert, Version, ConnectionStates).
+
+send_alert(Alert, #state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
+ connection_env = #connection_env{negotiated_version = Version},
+ ssl_options = #{log_level := LogLevel},
+ connection_states = ConnectionStates0} = StateData0) ->
+ {BinMsg, ConnectionStates} =
+ encode_alert(Alert, Version, ConnectionStates0),
+ tls_socket:send(Transport, Socket, BinMsg),
+ ssl_logger:debug(LogLevel, outbound, 'record', BinMsg),
+ StateData0#state{connection_states = ConnectionStates}.
+
+%% If an ALERT sent in the connection state, should cause the TLS
+%% connection to end, we need to synchronize with the tls_sender
+%% process so that the ALERT if possible (that is the tls_sender process is
+%% not blocked) is sent before the connection process terminates and
+%% thereby closes the transport socket.
+send_alert_in_connection(#alert{level = ?FATAL} = Alert, State) ->
+ send_sync_alert(Alert, State);
+send_alert_in_connection(#alert{description = ?CLOSE_NOTIFY} = Alert, State) ->
+ send_sync_alert(Alert, State);
+send_alert_in_connection(Alert,
+ #state{protocol_specific = #{sender := Sender}}) ->
+ tls_sender:send_alert(Sender, Alert).
+send_sync_alert(
+ Alert, #state{protocol_specific = #{sender := Sender}} = State) ->
+ try tls_sender:send_and_ack_alert(Sender, Alert)
+ catch
+ _:_ ->
+ throw({stop, {shutdown, own_alert}, State})
+ end.
+
+%% User closes or recursive call!
+close({close, Timeout}, Socket, Transport = gen_tcp, _,_) ->
+ tls_socket:setopts(Transport, Socket, [{active, false}]),
+ Transport:shutdown(Socket, write),
+ _ = Transport:recv(Socket, 0, Timeout),
+ ok;
+%% Peer closed socket
+close({shutdown, transport_closed}, Socket, Transport = gen_tcp, ConnectionStates, Check) ->
+ close({close, 0}, Socket, Transport, ConnectionStates, Check);
+%% We generate fatal alert
+close({shutdown, own_alert}, Socket, Transport = gen_tcp, ConnectionStates, Check) ->
+ %% Standard trick to try to make sure all
+ %% data sent to the tcp port is really delivered to the
+ %% peer application before tcp port is closed so that the peer will
+ %% get the correct TLS alert message and not only a transport close.
+ %% Will return when other side has closed or after timout millisec
+ %% e.g. we do not want to hang if something goes wrong
+ %% with the network but we want to maximise the odds that
+ %% peer application gets all data sent on the tcp connection.
+ close({close, ?DEFAULT_TIMEOUT}, Socket, Transport, ConnectionStates, Check);
+close(downgrade, _,_,_,_) ->
+ ok;
+%% Other
+close(_, Socket, Transport, _,_) ->
+ tls_socket:close(Transport, Socket).
+protocol_name() ->
+ "TLS".
+
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
+tls_handshake_events(Packets) ->
+ lists:map(fun(Packet) ->
+ {next_event, internal, {handshake, Packet}}
+ end, Packets).
+
+unprocessed_events(Events) ->
+ %% The first handshake event will be processed immediately
+ %% as it is entered first in the event queue and
+ %% when it is processed there will be length(Events)-1
+ %% handshake events left to process before we should
+ %% process more TLS-records received on the socket.
+ erlang:length(Events)-1.
+
+encode_handshake(Handshake, Version, ConnectionStates0, Hist0) ->
+ Frag = tls_handshake:encode_handshake(Handshake, Version),
+ Hist = ssl_handshake:update_handshake_history(Hist0, Frag),
+ {Encoded, ConnectionStates} =
+ tls_record:encode_handshake(Frag, Version, ConnectionStates0),
+ {Encoded, ConnectionStates, Hist}.
+
+encode_change_cipher(#change_cipher_spec{}, Version, ConnectionStates) ->
+ tls_record:encode_change_cipher_spec(Version, ConnectionStates).
+
+next_tls_record(Data, StateName,
+ #state{protocol_buffers =
+ #protocol_buffers{tls_record_buffer = Buf0,
+ tls_cipher_texts = CT0} = Buffers,
+ ssl_options = SslOpts} = State0) ->
+ Versions =
+ %% TLSPlaintext.legacy_record_version is ignored in TLS 1.3 and thus all
+ %% record version are accepted when receiving initial ClientHello and
+ %% ServerHello. This can happen in state 'hello' in case of all TLS
+ %% versions and also in state 'start' when TLS 1.3 is negotiated.
+ %% After the version is negotiated all subsequent TLS records shall have
+ %% the proper legacy_record_version (= negotiated_version).
+ %% Note: TLS record version {3,4} is used internally in TLS 1.3 and at this
+ %% point it is the same as the negotiated protocol version.
+ %% TODO: Refactor state machine and introduce a record_protocol_version beside
+ %% the negotiated_version.
+ case StateName of
+ State when State =:= hello orelse
+ State =:= start ->
+ [tls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_VERSIONS];
+ _ ->
+ State0#state.connection_env#connection_env.negotiated_version
+ end,
+ #{current_write := #{max_fragment_length := MaxFragLen}} = State0#state.connection_states,
+ case tls_record:get_tls_records(Data, Versions, Buf0, MaxFragLen, SslOpts) of
+ {Records, Buf1} ->
+ CT1 = CT0 ++ Records,
+ next_record(StateName, State0#state{protocol_buffers =
+ Buffers#protocol_buffers{tls_record_buffer = Buf1,
+ tls_cipher_texts = CT1}});
+ #alert{} = Alert ->
+ handle_record_alert(Alert, State0)
+ end.
+
+next_record(_, #state{handshake_env =
+ #handshake_env{unprocessed_handshake_events = N} = HsEnv}
+ = State) when N > 0 ->
+ {no_record, State#state{handshake_env =
+ HsEnv#handshake_env{unprocessed_handshake_events = N-1}}};
+next_record(_, #state{protocol_buffers =
+ #protocol_buffers{tls_cipher_texts = [_|_] = CipherTexts},
+ connection_states = ConnectionStates,
+ ssl_options = #{padding_check := Check}} = State) ->
+ next_record(State, CipherTexts, ConnectionStates, Check);
+next_record(connection, #state{protocol_buffers = #protocol_buffers{tls_cipher_texts = []},
+ protocol_specific = #{active_n_toggle := true}
+ } = State) ->
+ %% If ssl application user is not reading data wait to activate socket
+ flow_ctrl(State);
+
+next_record(_, #state{protocol_buffers = #protocol_buffers{tls_cipher_texts = []},
+ protocol_specific = #{active_n_toggle := true}
+ } = State) ->
+ activate_socket(State);
+next_record(_, State) ->
+ {no_record, State}.
+
+%%% bytes_to_read equals the integer Length arg of ssl:recv
+%%% the actual value is only relevant for packet = raw | 0
+%%% bytes_to_read = undefined means no recv call is ongoing
+flow_ctrl(#state{user_data_buffer = {_,Size,_},
+ socket_options = #socket_options{active = false},
+ bytes_to_read = undefined} = State) when Size =/= 0 ->
+ %% Passive mode wait for new recv request or socket activation
+ %% that is preserv some tcp back pressure by waiting to activate
+ %% socket
+ {no_record, State};
+%%%%%%%%%% A packet mode is set and socket is passive %%%%%%%%%%
+flow_ctrl(#state{socket_options = #socket_options{active = false,
+ packet = Packet}} = State)
+ when ((Packet =/= 0) andalso (Packet =/= raw)) ->
+ %% We need more data to complete the packet.
+ activate_socket(State);
+%%%%%%%%% No packet mode set and socket is passive %%%%%%%%%%%%
+flow_ctrl(#state{user_data_buffer = {_,Size,_},
+ socket_options = #socket_options{active = false},
+ bytes_to_read = 0} = State) when Size == 0 ->
+ %% Passive mode no available bytes, get some
+ activate_socket(State);
+flow_ctrl(#state{user_data_buffer = {_,Size,_},
+ socket_options = #socket_options{active = false},
+ bytes_to_read = 0} = State) when Size =/= 0 ->
+ %% There is data in the buffer to deliver
+ {no_record, State};
+flow_ctrl(#state{user_data_buffer = {_,Size,_},
+ socket_options = #socket_options{active = false},
+ bytes_to_read = BytesToRead} = State) when (BytesToRead > 0) ->
+ case (Size >= BytesToRead) of
+ true -> %% There is enough data bufferd
+ {no_record, State};
+ false -> %% We need more data to complete the delivery of <BytesToRead> size
+ activate_socket(State)
+ end;
+%%%%%%%%%%% Active mode or more data needed %%%%%%%%%%
+flow_ctrl(State) ->
+ activate_socket(State).
+
+
+activate_socket(#state{protocol_specific = #{active_n_toggle := true, active_n := N} = ProtocolSpec,
+ static_env = #static_env{socket = Socket,
+ close_tag = CloseTag,
+ transport_cb = Transport}
+ } = State) ->
+ case tls_socket:setopts(Transport, Socket, [{active, N}]) of
+ ok ->
+ {no_record, State#state{protocol_specific = ProtocolSpec#{active_n_toggle => false}}};
+ _ ->
+ self() ! {CloseTag, Socket},
+ {no_record, State}
+ end.
+
+%% Decipher next record and concatenate consecutive ?APPLICATION_DATA records into one
+%%
+next_record(State, CipherTexts, ConnectionStates, Check) ->
+ next_record(State, CipherTexts, ConnectionStates, Check, []).
+%%
+next_record(#state{connection_env = #connection_env{negotiated_version = {3,4} = Version}} = State,
+ [CT|CipherTexts], ConnectionStates0, Check, Acc) ->
+ case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of
+ {#ssl_tls{type = ?APPLICATION_DATA, fragment = Fragment}, ConnectionStates} ->
+ case CipherTexts of
+ [] ->
+ %% End of cipher texts - build and deliver an ?APPLICATION_DATA record
+ %% from the accumulated fragments
+ next_record_done(State, [], ConnectionStates,
+ #ssl_tls{type = ?APPLICATION_DATA,
+ fragment = iolist_to_binary(lists:reverse(Acc, [Fragment]))});
+ [_|_] ->
+ next_record(State, CipherTexts, ConnectionStates, Check, [Fragment|Acc])
+ end;
+ {Record, ConnectionStates} when Acc =:= [] ->
+ %% Singelton non-?APPLICATION_DATA record - deliver
+ next_record_done(State, CipherTexts, ConnectionStates, Record);
+ {_Record, _ConnectionStates_to_forget} ->
+ %% Not ?APPLICATION_DATA but we have accumulated fragments
+ %% -> build an ?APPLICATION_DATA record with concatenated fragments
+ %% and forget about decrypting this record - we'll decrypt it again next time
+ %% Will not work for stream ciphers
+ next_record_done(State, [CT|CipherTexts], ConnectionStates0,
+ #ssl_tls{type = ?APPLICATION_DATA, fragment = iolist_to_binary(lists:reverse(Acc))});
+ #alert{} = Alert ->
+ Alert
+ end;
+next_record(#state{connection_env = #connection_env{negotiated_version = Version}} = State,
+ [#ssl_tls{type = ?APPLICATION_DATA} = CT |CipherTexts], ConnectionStates0, Check, Acc) ->
+ case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of
+ {#ssl_tls{type = ?APPLICATION_DATA, fragment = Fragment}, ConnectionStates} ->
+ case CipherTexts of
+ [] ->
+ %% End of cipher texts - build and deliver an ?APPLICATION_DATA record
+ %% from the accumulated fragments
+ next_record_done(State, [], ConnectionStates,
+ #ssl_tls{type = ?APPLICATION_DATA,
+ fragment = iolist_to_binary(lists:reverse(Acc, [Fragment]))});
+ [_|_] ->
+ next_record(State, CipherTexts, ConnectionStates, Check, [Fragment|Acc])
+ end;
+ #alert{} = Alert ->
+ Alert
+ end;
+next_record(State, CipherTexts, ConnectionStates, _, [_|_] = Acc) ->
+ next_record_done(State, CipherTexts, ConnectionStates,
+ #ssl_tls{type = ?APPLICATION_DATA,
+ fragment = iolist_to_binary(lists:reverse(Acc))});
+next_record(#state{connection_env = #connection_env{negotiated_version = Version}} = State,
+ [CT|CipherTexts], ConnectionStates0, Check, []) ->
+ case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of
+ {Record, ConnectionStates} ->
+ %% Singelton non-?APPLICATION_DATA record - deliver
+ next_record_done(State, CipherTexts, ConnectionStates, Record);
+ #alert{} = Alert ->
+ Alert
+ end.
+
+next_record_done(#state{protocol_buffers = Buffers} = State, CipherTexts, ConnectionStates, Record) ->
+ {Record,
+ State#state{protocol_buffers = Buffers#protocol_buffers{tls_cipher_texts = CipherTexts},
+ connection_states = ConnectionStates}}.
+
+%% Special version handling for TLS 1.3 clients:
+%% In the shared state 'init' negotiated_version is set to requested version and
+%% that is expected by the legacy part of the state machine. However, in order to
+%% be able to process new TLS 1.3 extensions, the effective version shall be set
+%% {3,4}.
+%% When highest supported version is {3,4} the negotiated version is set to {3,3}.
+effective_version({3,3} , #{versions := [Version|_]}, client) when Version >= {3,4} ->
+ Version;
+%% Use highest supported version during startup (TLS server, all versions).
+effective_version(undefined, #{versions := [Version|_]}, _) ->
+ Version;
+%% Use negotiated version in all other cases.
+effective_version(Version, _, _) ->
+ Version.
+
+assert_buffer_sanity(<<?BYTE(_Type), ?UINT24(Length), Rest/binary>>,
+ #{max_handshake_size := Max}) when
+ Length =< Max ->
+ case size(Rest) of
+ N when N < Length ->
+ true;
+ N when N > Length ->
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
+ too_big_handshake_data));
+ _ ->
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
+ malformed_handshake_data))
+ end;
+assert_buffer_sanity(Bin, _) ->
+ case size(Bin) of
+ N when N < 3 ->
+ true;
+ _ ->
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
+ malformed_handshake_data))
+ end.
+
+decode_alerts(Bin) ->
+ ssl_alert:decode(Bin).
+
+handle_alerts([], Result) ->
+ Result;
+handle_alerts(_, {stop, _, _} = Stop) ->
+ Stop;
+handle_alerts([#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} | _Alerts],
+ {next_state, connection = StateName, #state{connection_env = CEnv,
+ socket_options = #socket_options{active = false},
+ start_or_recv_from = From} = State}) when From == undefined ->
+ {next_state, StateName, State#state{connection_env = CEnv#connection_env{terminated = true}}};
+handle_alerts([Alert | Alerts], {next_state, StateName, State}) ->
+ handle_alerts(Alerts, ssl_gen_statem:handle_alert(Alert, StateName, State));
+handle_alerts([Alert | Alerts], {next_state, StateName, State, _Actions}) ->
+ handle_alerts(Alerts, ssl_gen_statem:handle_alert(Alert, StateName, State)).
+
+handle_record_alert(Alert, _) ->
+ Alert.
+
diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl
index e1cc27069b..d7c899c7cf 100644
--- a/lib/ssl/src/tls_handshake.erl
+++ b/lib/ssl/src/tls_handshake.erl
@@ -325,28 +325,29 @@ handle_client_hello(Version,
signature_algs := SupportedHashSigns,
eccs := SupportedECCs,
honor_ecc_order := ECCOrder} = SslOpts,
- {SessIdTracker, Session0, ConnectionStates0, Cert, _},
+ {SessIdTracker, Session0, ConnectionStates0, OwnCerts, _},
Renegotiation) ->
+ OwnCert = ssl_handshake:select_own_cert(OwnCerts),
case tls_record:is_acceptable_version(Version, Versions) of
true ->
Curves = maps:get(elliptic_curves, HelloExt, undefined),
ClientHashSigns = maps:get(signature_algs, HelloExt, undefined),
ClientSignatureSchemes = maps:get(signature_algs_cert, HelloExt, undefined),
AvailableHashSigns = ssl_handshake:available_signature_algs(
- ClientHashSigns, SupportedHashSigns, Cert, Version),
+ ClientHashSigns, SupportedHashSigns, OwnCert, Version),
ECCCurve = ssl_handshake:select_curve(Curves, SupportedECCs, ECCOrder),
{Type, #session{cipher_suite = CipherSuite} = Session1}
= ssl_handshake:select_session(SugesstedId, CipherSuites,
AvailableHashSigns, Compressions,
SessIdTracker, Session0#session{ecc = ECCCurve},
- Version, SslOpts, Cert),
+ Version, SslOpts, OwnCert),
case CipherSuite of
no_suite ->
?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_ciphers);
_ ->
#{key_exchange := KeyExAlg} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
case ssl_handshake:select_hashsign({ClientHashSigns, ClientSignatureSchemes},
- Cert, KeyExAlg,
+ OwnCert, KeyExAlg,
SupportedHashSigns,
Version) of
#alert{} = Alert ->
diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl
index e2af29889f..dbde7ad476 100644
--- a/lib/ssl/src/tls_handshake_1_3.erl
+++ b/lib/ssl/src/tls_handshake_1_3.erl
@@ -178,8 +178,14 @@ encrypted_extensions(#state{handshake_env = HandshakeEnv}) ->
MaxFragEnum ->
E1#{max_frag_enum => MaxFragEnum}
end,
+ E = case HandshakeEnv#handshake_env.sni_guided_cert_selection of
+ false ->
+ E2;
+ true ->
+ E2#{sni => #sni{hostname = ""}}
+ end,
#encrypted_extensions{
- extensions = E2
+ extensions = E
}.
@@ -235,7 +241,11 @@ filter_tls13_algs(Algo) ->
%% opaque certificate_request_context<0..2^8-1>;
%% CertificateEntry certificate_list<0..2^24-1>;
%% } Certificate;
-certificate(OwnCert, CertDbHandle, CertDbRef, _CRContext, Role) ->
+certificate(undefined, _, _, _, client) ->
+ {ok, #certificate_1_3{
+ certificate_request_context = <<>>,
+ certificate_list = []}};
+certificate([OwnCert], CertDbHandle, CertDbRef, _CRContext, Role) ->
case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of
{ok, _, Chain} ->
CertList = chain_to_cert_list(Chain),
@@ -257,8 +267,12 @@ certificate(OwnCert, CertDbHandle, CertDbRef, _CRContext, Role) ->
{ok, #certificate_1_3{
certificate_request_context = <<>>,
certificate_list = []}}
- end.
-
+ end;
+certificate([_,_| _] = Chain, _,_,_,_) ->
+ CertList = chain_to_cert_list(Chain),
+ {ok, #certificate_1_3{
+ certificate_request_context = <<>>,
+ certificate_list = CertList}}.
certificate_verify(PrivateKey, SignatureScheme,
#state{connection_states = ConnectionStates,
@@ -575,39 +589,41 @@ build_content(Context, THash) ->
%% TLS Server
do_start(#client_hello{cipher_suites = ClientCiphers,
session_id = SessionId,
- extensions = Extensions} = _Hello,
- #state{connection_states = ConnectionStates0,
- ssl_options = #{ciphers := ServerCiphers,
+ extensions = Extensions} = Hello,
+ #state{ssl_options = #{ciphers := ServerCiphers,
signature_algs := ServerSignAlgs,
supported_groups := ServerGroups0,
alpn_preferred_protocols := ALPNPreferredProtocols,
- honor_cipher_order := HonorCipherOrder},
- session = #session{own_certificate = Cert}} = State0) ->
-
+ keep_secrets := KeepSecrets,
+ honor_cipher_order := HonorCipherOrder}} = State0) ->
+ SNI = maps:get(sni, Extensions, undefined),
ClientGroups0 = maps:get(elliptic_curves, Extensions, undefined),
- ClientGroups = get_supported_groups(ClientGroups0),
- ServerGroups = get_supported_groups(ServerGroups0),
-
- ClientShares0 = maps:get(key_share, Extensions, undefined),
- ClientShares = get_key_shares(ClientShares0),
-
- OfferedPSKs = get_offered_psks(Extensions),
-
- ClientALPN0 = maps:get(alpn, Extensions, undefined),
- ClientALPN = ssl_handshake:decode_alpn(ClientALPN0),
-
- ClientSignAlgs = get_signature_scheme_list(
- maps:get(signature_algs, Extensions, undefined)),
- ClientSignAlgsCert = get_signature_scheme_list(
- maps:get(signature_algs_cert, Extensions, undefined)),
-
- CookieExt = maps:get(cookie, Extensions, undefined),
- Cookie = get_cookie(CookieExt),
-
{Ref,Maybe} = maybe(),
-
try
- Maybe(validate_cookie(Cookie, State0)),
+ ClientGroups = Maybe(get_supported_groups(ClientGroups0)),
+ ServerGroups = Maybe(get_supported_groups(ServerGroups0)),
+
+ ClientShares0 = maps:get(key_share, Extensions, undefined),
+ ClientShares = get_key_shares(ClientShares0),
+
+ OfferedPSKs = get_offered_psks(Extensions),
+
+ ClientALPN0 = maps:get(alpn, Extensions, undefined),
+ ClientALPN = ssl_handshake:decode_alpn(ClientALPN0),
+
+ ClientSignAlgs = get_signature_scheme_list(
+ maps:get(signature_algs, Extensions, undefined)),
+ ClientSignAlgsCert = get_signature_scheme_list(
+ maps:get(signature_algs_cert, Extensions, undefined)),
+
+ CookieExt = maps:get(cookie, Extensions, undefined),
+ Cookie = get_cookie(CookieExt),
+
+ #state{connection_states = ConnectionStates0,
+ session = #session{own_certificates = [Cert | _]}} = State1 =
+ Maybe(ssl_gen_statem:handle_sni_extension(SNI, State0)),
+
+ Maybe(validate_cookie(Cookie, State1)),
%% Handle ALPN extension if ALPN is configured
ALPNProtocol = Maybe(handle_alpn(ALPNPreferredProtocols, ClientALPN)),
@@ -636,24 +652,30 @@ do_start(#client_hello{cipher_suites = ClientCiphers,
%% Generate server_share
KeyShare = ssl_cipher:generate_server_share(Group),
- State1 = case maps:get(max_frag_enum, Extensions, undefined) of
+ State2 = case maps:get(max_frag_enum, Extensions, undefined) of
MaxFragEnum when is_record(MaxFragEnum, max_frag_enum) ->
ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0),
- HsEnv1 = (State0#state.handshake_env)#handshake_env{max_frag_enum = MaxFragEnum},
- State0#state{handshake_env = HsEnv1,
+ HsEnv1 = (State1#state.handshake_env)#handshake_env{max_frag_enum = MaxFragEnum},
+ State1#state{handshake_env = HsEnv1,
connection_states = ConnectionStates1};
_ ->
- State0
+ State1
end,
- State2 = update_start_state(State1,
- #{cipher => Cipher,
- key_share => KeyShare,
- session_id => SessionId,
- group => Group,
- sign_alg => SelectedSignAlg,
- peer_public_key => ClientPubKey,
- alpn => ALPNProtocol}),
+ State3 = if KeepSecrets =:= true ->
+ set_client_random(State2, Hello#client_hello.random);
+ true ->
+ State2
+ end,
+
+ State = update_start_state(State3,
+ #{cipher => Cipher,
+ key_share => KeyShare,
+ session_id => SessionId,
+ group => Group,
+ sign_alg => SelectedSignAlg,
+ peer_public_key => ClientPubKey,
+ alpn => ALPNProtocol}),
%% 4.1.4. Hello Retry Request
%%
@@ -661,12 +683,12 @@ do_start(#client_hello{cipher_suites = ClientCiphers,
%% message if it is able to find an acceptable set of parameters but the
%% ClientHello does not contain sufficient information to proceed with
%% the handshake.
- case Maybe(send_hello_retry_request(State2, ClientPubKey, KeyShare, SessionId)) of
+ case Maybe(send_hello_retry_request(State, ClientPubKey, KeyShare, SessionId)) of
{_, start} = NextStateTuple ->
NextStateTuple;
{_, negotiated} = NextStateTuple ->
%% Exclude any incompatible PSKs.
- PSK = Maybe(handle_pre_shared_key(State2, OfferedPSKs, Cipher)),
+ PSK = Maybe(handle_pre_shared_key(State, OfferedPSKs, Cipher)),
Maybe(session_resumption(NextStateTuple, PSK))
end
catch
@@ -680,6 +702,7 @@ do_start(#server_hello{cipher_suite = SelectedCipherSuite,
#state{static_env = #static_env{role = client,
host = Host,
port = Port,
+ protocol_cb = Connection,
transport_cb = Transport,
socket = Socket},
handshake_env = #handshake_env{renegotiation = {Renegotiation, _},
@@ -690,15 +713,15 @@ do_start(#server_hello{cipher_suite = SelectedCipherSuite,
use_ticket := UseTicket,
session_tickets := SessionTickets,
log_level := LogLevel} = SslOpts,
- session = #session{own_certificate = Cert} = Session0,
+ session = #session{own_certificates = OwnCerts} = Session0,
connection_states = ConnectionStates0
} = State0) ->
- ClientGroups = get_supported_groups(ClientGroups0),
- CookieExt = maps:get(cookie, Extensions, undefined),
- Cookie = get_cookie(CookieExt),
-
{Ref,Maybe} = maybe(),
try
+ ClientGroups = Maybe(get_supported_groups(ClientGroups0)),
+ CookieExt = maps:get(cookie, Extensions, undefined),
+ Cookie = get_cookie(CookieExt),
+
ServerKeyShare = maps:get(key_share, Extensions, undefined),
SelectedGroup = get_selected_group(ServerKeyShare),
@@ -722,7 +745,7 @@ do_start(#server_hello{cipher_suite = SelectedCipherSuite,
TicketData = get_ticket_data(self(), SessionTickets, UseTicket),
OcspNonce = maps:get(ocsp_nonce, OcspState, undefined),
Hello0 = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
- SessionId, Renegotiation, Cert, ClientKeyShare,
+ SessionId, Renegotiation, OwnCerts, ClientKeyShare,
TicketData, OcspNonce),
%% Echo cookie received in HelloRetryrequest
Hello1 = maybe_add_cookie_extension(Cookie, Hello0),
@@ -742,7 +765,7 @@ do_start(#server_hello{cipher_suite = SelectedCipherSuite,
Hello = tls_handshake_1_3:maybe_add_binders(Hello1, HHistory0, TicketData, NegotiatedVersion),
{BinMsg0, ConnectionStates, HHistory} =
- tls_connection:encode_handshake(Hello, NegotiatedVersion, ConnectionStates0, HHistory0),
+ Connection:encode_handshake(Hello, NegotiatedVersion, ConnectionStates0, HHistory0),
%% D.4. Middlebox Compatibility Mode
{#state{handshake_env = HsEnv} = State3, BinMsg} =
@@ -767,6 +790,7 @@ do_start(#server_hello{cipher_suite = SelectedCipherSuite,
do_negotiated({start_handshake, PSK0},
#state{connection_states = ConnectionStates0,
+ static_env = #static_env{protocol_cb = Connection},
session = #session{session_id = SessionId,
ecc = SelectedGroup,
dh_public_value = ClientPublicKey},
@@ -783,10 +807,10 @@ do_negotiated({start_handshake, PSK0},
try
%% Create server_hello
ServerHello = server_hello(server_hello, SessionId, KeyShare, PSK0, ConnectionStates0),
- State1 = tls_connection:queue_handshake(ServerHello, State0),
+ State1 = Connection:queue_handshake(ServerHello, State0),
%% D.4. Middlebox Compatibility Mode
State2 = maybe_queue_change_cipher_spec(State1, last),
- {State3, _} = tls_connection:send_handshake_flight(State2),
+ {State3, _} = Connection:send_handshake_flight(State2),
PSK = get_pre_shared_key(PSK0, HKDF),
@@ -800,7 +824,7 @@ do_negotiated({start_handshake, PSK0},
EncryptedExtensions = encrypted_extensions(State5),
%% Encode EncryptedExtensions
- State6 = tls_connection:queue_handshake(EncryptedExtensions, State5),
+ State6 = Connection:queue_handshake(EncryptedExtensions, State5),
%% Create and send CertificateRequest ({verify, verify_peer})
{State7, NextState} = maybe_send_certificate_request(State6, SslOpts, PSK0),
@@ -815,10 +839,10 @@ do_negotiated({start_handshake, PSK0},
Finished = finished(State9),
%% Encode Finished
- State10= tls_connection:queue_handshake(Finished, State9),
+ State10= Connection:queue_handshake(Finished, State9),
%% Send first flight
- {State, _} = tls_connection:send_handshake_flight(State10),
+ {State, _} = Connection:send_handshake_flight(State10),
{State, NextState}
@@ -874,34 +898,27 @@ do_wait_finished(#finished{verify_data = VerifyData},
end;
%% TLS Client
do_wait_finished(#finished{verify_data = VerifyData},
- #state{static_env = #static_env{role = client}} = State0) ->
-
+ #state{static_env = #static_env{role = client,
+ protocol_cb = Connection}} = State0) ->
+
{Ref,Maybe} = maybe(),
try
Maybe(validate_finished(State0, VerifyData)),
-
%% D.4. Middlebox Compatibility Mode
State1 = maybe_queue_change_cipher_spec(State0, first),
-
%% Maybe send Certificate + CertificateVerify
State2 = Maybe(maybe_queue_cert_cert_cv(State1)),
-
Finished = finished(State2),
-
%% Encode Finished
- State3 = tls_connection:queue_handshake(Finished, State2),
-
+ State3 = Connection:queue_handshake(Finished, State2),
%% Send first flight
- {State4, _} = tls_connection:send_handshake_flight(State3),
-
+ {State4, _} = Connection:send_handshake_flight(State3),
State5 = calculate_traffic_secrets(State4),
State6 = maybe_calculate_resumption_master_secret(State5),
State7 = forget_master_secret(State6),
-
%% Configure traffic keys
ssl_record:step_encryption_state(State7)
-
catch
{Ref, #alert{} = Alert} ->
Alert
@@ -916,14 +933,15 @@ do_wait_sh(#server_hello{cipher_suite = SelectedCipherSuite,
supported_groups := ClientGroups0,
session_tickets := SessionTickets,
use_ticket := UseTicket}} = State0) ->
- ClientGroups = get_supported_groups(ClientGroups0),
- ServerKeyShare0 = maps:get(key_share, Extensions, undefined),
- ServerPreSharedKey = maps:get(pre_shared_key, Extensions, undefined),
- SelectedIdentity = get_selected_identity(ServerPreSharedKey),
- ClientKeyShare = get_key_shares(ClientKeyShare0),
-
+
{Ref,Maybe} = maybe(),
try
+ ClientGroups = Maybe(get_supported_groups(ClientGroups0)),
+ ServerKeyShare0 = maps:get(key_share, Extensions, undefined),
+ ServerPreSharedKey = maps:get(pre_shared_key, Extensions, undefined),
+ SelectedIdentity = get_selected_identity(ServerPreSharedKey),
+ ClientKeyShare = get_key_shares(ClientKeyShare0),
+
%% Go to state 'start' if server replies with 'HelloRetryRequest'.
Maybe(maybe_hello_retry_request(ServerHello, State0)),
@@ -1108,12 +1126,13 @@ maybe_queue_cert_cert_cv(#state{client_certificate_requested = false} = State) -
{ok, State};
maybe_queue_cert_cert_cv(#state{connection_states = _ConnectionStates0,
session = #session{session_id = _SessionId,
- own_certificate = OwnCert},
+ own_certificates = OwnCerts},
ssl_options = #{} = _SslOpts,
key_share = _KeyShare,
handshake_env = #handshake_env{tls_handshake_history = _HHistory0},
static_env = #static_env{
role = client,
+ protocol_cb = Connection,
cert_db = CertDbHandle,
cert_db_ref = CertDbRef,
socket = _Socket,
@@ -1122,11 +1141,10 @@ maybe_queue_cert_cert_cv(#state{connection_states = _ConnectionStates0,
{Ref,Maybe} = maybe(),
try
%% Create Certificate
- Certificate = Maybe(certificate(OwnCert, CertDbHandle, CertDbRef, <<>>, client)),
+ Certificate = Maybe(certificate(OwnCerts, CertDbHandle, CertDbRef, <<>>, client)),
%% Encode Certificate
- State1 = tls_connection:queue_handshake(Certificate, State0),
-
+ State1 = Connection:queue_handshake(Certificate, State0),
%% Maybe create and queue CertificateVerify
State = Maybe(maybe_queue_cert_verify(Certificate, State1)),
{ok, State}
@@ -1144,12 +1162,13 @@ maybe_queue_cert_verify(_Certificate,
#state{connection_states = _ConnectionStates0,
session = #session{sign_alg = SignatureScheme},
connection_env = #connection_env{private_key = CertPrivateKey},
- static_env = #static_env{role = client}
+ static_env = #static_env{role = client,
+ protocol_cb = Connection}
} = State) ->
{Ref,Maybe} = maybe(),
try
CertificateVerify = Maybe(certificate_verify(CertPrivateKey, SignatureScheme, State, client)),
- {ok, tls_connection:queue_handshake(CertificateVerify, State)}
+ {ok, Connection:queue_handshake(CertificateVerify, State)}
catch
{Ref, #alert{} = Alert} ->
{error, Alert}
@@ -1182,15 +1201,16 @@ compare_verify_data(_, _) ->
{error, ?ALERT_REC(?FATAL, ?DECRYPT_ERROR, decrypt_error)}.
-send_hello_retry_request(#state{connection_states = ConnectionStates0} = State0,
+send_hello_retry_request(#state{connection_states = ConnectionStates0,
+ static_env = #static_env{protocol_cb = Connection}} = State0,
no_suitable_key, KeyShare, SessionId) ->
ServerHello0 = server_hello(hello_retry_request, SessionId, KeyShare, undefined, ConnectionStates0),
{State1, ServerHello} = maybe_add_cookie_extension(State0, ServerHello0),
- State2 = tls_connection:queue_handshake(ServerHello, State1),
+ State2 = Connection:queue_handshake(ServerHello, State1),
%% D.4. Middlebox Compatibility Mode
State3 = maybe_queue_change_cipher_spec(State2, last),
- {State4, _} = tls_connection:send_handshake_flight(State3),
+ {State4, _} = Connection:send_handshake_flight(State3),
%% Update handshake history
State5 = replace_ch1_with_message_hash(State4),
@@ -1216,22 +1236,23 @@ maybe_send_certificate_request(State, _, PSK) when PSK =/= undefined ->
{State, wait_finished};
maybe_send_certificate_request(State, #{verify := verify_none}, _) ->
{State, wait_finished};
-maybe_send_certificate_request(State, #{verify := verify_peer,
- signature_algs := SignAlgs,
- signature_algs_cert := SignAlgsCert}, _) ->
+maybe_send_certificate_request(#state{static_env = #static_env{protocol_cb = Connection}} = State,
+ #{verify := verify_peer,
+ signature_algs := SignAlgs,
+ signature_algs_cert := SignAlgsCert}, _) ->
CertificateRequest = certificate_request(SignAlgs, SignAlgsCert),
- {tls_connection:queue_handshake(CertificateRequest, State), wait_cert}.
-
+ {Connection:queue_handshake(CertificateRequest, State), wait_cert}.
maybe_send_certificate(State, PSK) when PSK =/= undefined ->
{ok, State};
-maybe_send_certificate(#state{session = #session{own_certificate = OwnCert},
+maybe_send_certificate(#state{session = #session{own_certificates = OwnCerts},
static_env = #static_env{
+ protocol_cb = Connection,
cert_db = CertDbHandle,
cert_db_ref = CertDbRef}} = State, _) ->
- case certificate(OwnCert, CertDbHandle, CertDbRef, <<>>, server) of
+ case certificate(OwnCerts, CertDbHandle, CertDbRef, <<>>, server) of
{ok, Certificate} ->
- {ok, tls_connection:queue_handshake(Certificate, State)};
+ {ok, Connection:queue_handshake(Certificate, State)};
Error ->
Error
end.
@@ -1240,11 +1261,12 @@ maybe_send_certificate(#state{session = #session{own_certificate = OwnCert},
maybe_send_certificate_verify(State, PSK) when PSK =/= undefined ->
{ok, State};
maybe_send_certificate_verify(#state{session = #session{sign_alg = SignatureScheme},
+ static_env = #static_env{protocol_cb = Connection},
connection_env = #connection_env{
private_key = CertPrivateKey}} = State, _) ->
case certificate_verify(CertPrivateKey, SignatureScheme, State, server) of
{ok, CertificateVerify} ->
- {ok, tls_connection:queue_handshake(CertificateVerify, State)};
+ {ok, Connection:queue_handshake(CertificateVerify, State)};
Error ->
Error
end.
@@ -1266,14 +1288,17 @@ maybe_send_session_ticket(#state{ssl_options = #{session_tickets := disabled}} =
maybe_send_session_ticket(State, 0) ->
State;
maybe_send_session_ticket(#state{connection_states = ConnectionStates,
- static_env = #static_env{trackers = Trackers}} = State0, N) ->
+ static_env = #static_env{trackers = Trackers,
+ protocol_cb = Connection}
+
+ } = State0, N) ->
Tracker = proplists:get_value(session_tickets_tracker, Trackers),
#{security_parameters := SecParamsR} =
ssl_record:current_connection_state(ConnectionStates, read),
#security_parameters{prf_algorithm = HKDF,
resumption_master_secret = RMS} = SecParamsR,
Ticket = tls_server_session_ticket:new(Tracker, HKDF, RMS),
- {State, _} = tls_connection:send_handshake(Ticket, State0),
+ {State, _} = Connection:send_handshake(Ticket, State0),
maybe_send_session_ticket(State, N - 1).
create_change_cipher_spec(#state{ssl_options = #{log_level := LogLevel}}) ->
@@ -1296,12 +1321,13 @@ create_change_cipher_spec(#state{ssl_options = #{log_level := LogLevel}}) ->
[BinChangeCipher].
process_certificate_request(#certificate_request_1_3{},
- #state{session = #session{own_certificate = undefined}} = State) ->
+ #state{session = #session{own_certificates = undefined}} = State) ->
{ok, {State#state{client_certificate_requested = true}, wait_cert}};
process_certificate_request(#certificate_request_1_3{
extensions = Extensions},
- #state{session = #session{own_certificate = Cert} = Session} = State) ->
+ #state{session = #session{own_certificates = [Cert|_]} = Session} =
+ State) ->
ServerSignAlgs = get_signature_scheme_list(
maps:get(signature_algs, Extensions, undefined)),
ServerSignAlgsCert = get_signature_scheme_list(
@@ -1316,7 +1342,7 @@ process_certificate_request(#certificate_request_1_3{
{error, _} ->
%% Certificate not supported: send empty certificate in state 'wait_finished'
{ok, {State#state{client_certificate_requested = true,
- session = Session#session{own_certificate = undefined}}, wait_cert}}
+ session = Session#session{own_certificates = undefined}}, wait_cert}}
end.
@@ -1331,7 +1357,6 @@ process_certificate(#certificate_1_3{
certificate_list = []},
#state{ssl_options =
#{fail_if_no_peer_cert := true}} = State0) ->
-
%% At this point the client believes that the connection is up and starts using
%% its traffic secrets. In order to be able send an proper Alert to the client
%% the server should also change its connection state and use the traffic
@@ -1377,57 +1402,31 @@ update_encryption_state(client, State) ->
validate_certificate_chain(CertEntries, CertDbHandle, CertDbRef,
#{server_name_indication := ServerNameIndication,
partial_chain := PartialChain,
- verify_fun := VerifyFun,
- customize_hostname_check := CustomizeHostnameCheck,
- crl_check := CrlCheck,
- log_level := LogLevel,
- depth := Depth,
- ocsp_responder_certs := OcspResponderCerts,
- signature_algs := SignAlgs,
- signature_algs_cert := SignAlgsCert
+ ocsp_responder_certs := OcspResponderCerts
} = SslOptions, CRLDbHandle, Role, Host, OcspState0) ->
{Certs, CertExt, OcspState} = split_cert_entries(CertEntries, OcspState0),
ServerName = ssl_handshake:server_name(ServerNameIndication, Host, Role),
- [PeerCert | ChainCerts ] = Certs,
+ [PeerCert | _ChainCerts ] = Certs,
try
- {TrustedCert, CertPath} =
- ssl_certificate:trusted_cert_and_path(Certs, CertDbHandle, CertDbRef,
+ PathsAndAnchors =
+ ssl_certificate:trusted_cert_and_paths(Certs, CertDbHandle, CertDbRef,
PartialChain),
- ValidationFunAndState =
- ssl_handshake:validation_fun_and_state(VerifyFun, #{role => Role,
- certdb => CertDbHandle,
- certdb_ref => CertDbRef,
- server_name => ServerName,
- customize_hostname_check =>
- CustomizeHostnameCheck,
- crl_check => CrlCheck,
- crl_db => CRLDbHandle,
- signature_algs => filter_tls13_algs(SignAlgs),
- signature_algs_cert => filter_tls13_algs(SignAlgsCert),
- version => {3,4},
- issuer => TrustedCert,
- cert_ext => CertExt,
- ocsp_responder_certs => OcspResponderCerts,
- ocsp_state => OcspState
- },
- CertPath, LogLevel),
- Options = [{max_path_length, Depth},
- {verify_fun, ValidationFunAndState}],
- case public_key:pkix_path_validation(TrustedCert, CertPath, Options) of
- {ok, {PublicKeyInfo,_}} ->
- {ok, {PeerCert, PublicKeyInfo}};
- {error, Reason} ->
- {ok, ssl_handshake:handle_path_validation_error(Reason, PeerCert, ChainCerts,
- SslOptions, Options,
- CertDbHandle, CertDbRef)}
- end
- catch
- error:{badmatch,{error, {asn1, Asn1Reason}}} ->
- %% ASN-1 decode of certificate somehow failed
- {error, ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, {failed_to_decode_certificate, Asn1Reason})};
- error:OtherReason ->
- {error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {unexpected_error, OtherReason})}
- end.
+ case path_validate(PathsAndAnchors, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle,
+ {3, 4}, SslOptions, #{cert_ext => CertExt,
+ ocsp_state => OcspState,
+ ocsp_responder_certs => OcspResponderCerts}) of
+ {ok, {PublicKeyInfo,_}} ->
+ {ok, {PeerCert, PublicKeyInfo}};
+ {error, Reason} ->
+ {ok, ssl_handshake:path_validation_alert(Reason)}
+ end
+ catch
+ error:{badmatch,{error, {asn1, Asn1Reason}}} ->
+ %% ASN-1 decode of certificate somehow failed
+ {error, ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, {failed_to_decode_certificate, Asn1Reason})};
+ error:OtherReason ->
+ {error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {unexpected_error, OtherReason})}
+ end.
store_peer_cert(#state{session = Session,
handshake_env = HsEnv} = State, PeerCert, PublicKeyInfo) ->
@@ -1524,7 +1523,9 @@ calculate_handshake_secrets(PublicKey, PrivateKey, SelectedGroup, PSK,
ReadFinishedKey = tls_v1:finished_key(ClientHSTrafficSecret, HKDFAlgo),
WriteFinishedKey = tls_v1:finished_key(ServerHSTrafficSecret, HKDFAlgo),
- update_pending_connection_states(State0, HandshakeSecret, undefined,
+ State1 = maybe_store_handshake_traffic_secret(State0, ClientHSTrafficSecret, ServerHSTrafficSecret),
+
+ update_pending_connection_states(State1, HandshakeSecret, undefined,
undefined, undefined,
ReadKey, ReadIV, ReadFinishedKey,
WriteKey, WriteIV, WriteFinishedKey).
@@ -1681,6 +1682,36 @@ overwrite_master_secret(ConnectionState = #{security_parameters := SecurityParam
ConnectionState#{security_parameters => SecurityParameters}.
+set_client_random(#state{connection_states =
+ #{pending_read := PendingRead,
+ pending_write := PendingWrite,
+ current_read := CurrentRead,
+ current_write := CurrentWrite} = CS} = State, ClientRandom) ->
+ State#state{connection_states = CS#{pending_read => overwrite_client_random(PendingRead, ClientRandom),
+ pending_write => overwrite_client_random(PendingWrite, ClientRandom),
+ current_read => overwrite_client_random(CurrentRead, ClientRandom),
+ current_write => overwrite_client_random(CurrentWrite, ClientRandom)}}.
+
+
+overwrite_client_random(ConnectionState = #{security_parameters := SecurityParameters0}, ClientRandom) ->
+ SecurityParameters = SecurityParameters0#security_parameters{client_random = ClientRandom},
+ ConnectionState#{security_parameters => SecurityParameters}.
+
+
+maybe_store_handshake_traffic_secret(#state{connection_states =
+ #{pending_read := PendingRead} = CS,
+ ssl_options = #{keep_secrets := true}} = State,
+ ClientHSTrafficSecret, ServerHSTrafficSecret) ->
+ PendingRead1 = store_handshake_traffic_secret(PendingRead, ClientHSTrafficSecret, ServerHSTrafficSecret),
+ State#state{connection_states = CS#{pending_read => PendingRead1}};
+maybe_store_handshake_traffic_secret(State, _, _) ->
+ State.
+
+store_handshake_traffic_secret(ConnectionState, ClientHSTrafficSecret, ServerHSTrafficSecret) ->
+ ConnectionState#{client_handshake_traffic_secret => ClientHSTrafficSecret,
+ server_handshake_traffic_secret => ServerHSTrafficSecret}.
+
+
update_pending_connection_states(#state{
static_env = #static_env{role = server},
connection_states =
@@ -2249,8 +2280,10 @@ get_signature_scheme_list(#signature_algorithms{
lists:filter(fun (E) -> is_atom(E) andalso E =/= unassigned end,
ClientSignatureSchemes).
+get_supported_groups(undefined = Groups) ->
+ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, {supported_groups, Groups})};
get_supported_groups(#supported_groups{supported_groups = Groups}) ->
- Groups.
+ {ok, Groups}.
get_key_shares(#key_share_client_hello{client_shares = ClientShares}) ->
ClientShares;
@@ -2523,3 +2556,53 @@ process_ticket(Bin, N) when is_binary(Bin) ->
%% (see Section 4.6.1), modulo 2^32.
obfuscate_ticket_age(TicketAge, AgeAdd) ->
(TicketAge + AgeAdd) rem round(math:pow(2,32)).
+
+path_validate([{TrustedCert, Path}], ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle,
+ Version, SslOptions, CertExt) ->
+ path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef,
+ CRLDbHandle, Version, SslOptions, CertExt);
+path_validate([{TrustedCert, Path} | Rest], ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle,
+ Version, SslOptions, CertExt) ->
+ case path_validation(TrustedCert, Path, ServerName,
+ Role, CertDbHandle, CertDbRef, CRLDbHandle,
+ Version, SslOptions, CertExt) of
+ {ok, _} = Result ->
+ Result;
+ {error, _} ->
+ path_validate(Rest, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle,
+ Version, SslOptions, CertExt)
+ end.
+
+path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, Version,
+ #{verify_fun := VerifyFun,
+ customize_hostname_check := CustomizeHostnameCheck,
+ crl_check := CrlCheck,
+ log_level := LogLevel,
+ signature_algs := SignAlgos,
+ signature_algs_cert := SignAlgosCert,
+ depth := Depth},
+ #{cert_ext := CertExt,
+ ocsp_responder_certs := OcspResponderCerts,
+ ocsp_state := OcspState}) ->
+ ValidationFunAndState =
+ ssl_handshake:validation_fun_and_state(VerifyFun, #{role => Role,
+ certdb => CertDbHandle,
+ certdb_ref => CertDbRef,
+ server_name => ServerName,
+ customize_hostname_check =>
+ CustomizeHostnameCheck,
+ crl_check => CrlCheck,
+ crl_db => CRLDbHandle,
+ signature_algs => filter_tls13_algs(SignAlgos),
+ signature_algs_cert =>
+ filter_tls13_algs(SignAlgosCert),
+ version => Version,
+ issuer => TrustedCert,
+ cert_ext => CertExt,
+ ocsp_responder_certs => OcspResponderCerts,
+ ocsp_state => OcspState
+ },
+ Path, LogLevel),
+ Options = [{max_path_length, Depth},
+ {verify_fun, ValidationFunAndState}],
+ public_key:pkix_path_validation(TrustedCert, Path, Options).
diff --git a/lib/ssl/src/tls_sender.erl b/lib/ssl/src/tls_sender.erl
index 51cf0e6073..3d2cafa24c 100644
--- a/lib/ssl/src/tls_sender.erl
+++ b/lib/ssl/src/tls_sender.erl
@@ -272,13 +272,13 @@ connection({call, From}, dist_get_tls_socket,
socket = Socket,
connection_pid = Pid,
trackers = Trackers}} = StateData) ->
- TLSSocket = tls_connection:socket([Pid, self()], Transport, Socket, Trackers),
+ TLSSocket = tls_gen_connection:socket([Pid, self()], Transport, Socket, Trackers),
{next_state, ?FUNCTION_NAME, StateData, [{reply, From, {ok, TLSSocket}}]};
connection({call, From}, {dist_handshake_complete, _Node, DHandle},
#data{static = #static{connection_pid = Pid} = Static} = StateData) ->
false = erlang:dist_ctrl_set_opt(DHandle, get_size, true),
ok = erlang:dist_ctrl_input_handler(DHandle, Pid),
- ok = ssl_connection:dist_handshake_complete(Pid, DHandle),
+ ok = ssl_gen_statem:dist_handshake_complete(Pid, DHandle),
%% From now on we execute on normal priority
process_flag(priority, normal),
{keep_state, StateData#data{static = Static#static{dist_handle = DHandle}},
@@ -454,7 +454,7 @@ send_application_data(Data, From, StateName,
{next_event, internal, {key_update, From}},
{next_event, internal, {application_packets, From, Data}}]};
renegotiate ->
- ssl_connection:internal_renegotiation(Pid, ConnectionStates0),
+ tls_dtls_connection:internal_renegotiation(Pid, ConnectionStates0),
{next_state, handshake, StateData0,
[{next_event, internal, {application_packets, From, Data}}]};
chunk_and_key_update ->
@@ -513,7 +513,7 @@ send_post_handshake_data(Handshake, From, StateName,
maybe_update_cipher_key(#data{connection_states = ConnectionStates0,
static = Static0} = StateData, #key_update{}) ->
- ConnectionStates = tls_connection:update_cipher_key(current_write, ConnectionStates0),
+ ConnectionStates = tls_connection_1_3:update_cipher_key(current_write, ConnectionStates0),
Static = Static0#static{bytes_sent = 0},
StateData#data{connection_states = ConnectionStates,
static = Static};
diff --git a/lib/ssl/src/tls_server_session_ticket_sup.erl b/lib/ssl/src/tls_server_session_ticket_sup.erl
index 7ee4bb7b2c..bdde94ecea 100644
--- a/lib/ssl/src/tls_server_session_ticket_sup.erl
+++ b/lib/ssl/src/tls_server_session_ticket_sup.erl
@@ -27,26 +27,34 @@
-behaviour(supervisor).
%% API
--export([start_link/0, start_link_dist/0]).
--export([start_child/1, start_child_dist/1]).
+-export([start_link/0,
+ start_link_dist/0]).
+-export([start_child/1,
+ start_child_dist/1]).
%% Supervisor callback
--export([init/1]).
+-export([init/1,
+ sup_name/1]).
%%%=========================================================================
%%% API
%%%=========================================================================
start_link() ->
- supervisor:start_link({local, tracker_name(normal)}, ?MODULE, []).
+ supervisor:start_link({local, sup_name(normal)}, ?MODULE, []).
start_link_dist() ->
- supervisor:start_link({local, tracker_name(dist)}, ?MODULE, []).
+ supervisor:start_link({local, sup_name(dist)}, ?MODULE, []).
start_child(Args) ->
- supervisor:start_child(tracker_name(normal), Args).
+ supervisor:start_child(sup_name(normal), Args).
start_child_dist(Args) ->
- supervisor:start_child(tracker_name(dist), Args).
+ supervisor:start_child(sup_name(dist), Args).
+
+sup_name(normal) ->
+ ?MODULE;
+sup_name(dist) ->
+ list_to_atom(atom_to_list(?MODULE) ++ "_dist").
%%%=========================================================================
%%% Supervisor callback
@@ -66,7 +74,3 @@ init(_O) ->
ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules},
{ok, {{RestartStrategy, MaxR, MaxT}, [ChildSpec]}}.
-tracker_name(normal) ->
- ?MODULE;
-tracker_name(dist) ->
- list_to_atom(atom_to_list(?MODULE) ++ "dist").
diff --git a/lib/ssl/src/tls_server_sup.erl b/lib/ssl/src/tls_server_sup.erl
index 5d3278fe32..b2f011f221 100644
--- a/lib/ssl/src/tls_server_sup.erl
+++ b/lib/ssl/src/tls_server_sup.erl
@@ -47,10 +47,12 @@ init([]) ->
ListenTracker = listen_options_tracker_child_spec(),
SessionTracker = tls_server_session_child_spec(),
Pre_1_3SessionTracker = ssl_server_session_child_spec(),
-
+ Pre_1_3UpgradeSessionTracker = ssl_upgrade_server_session_child_spec(),
+
{ok, {{one_for_all, 10, 3600}, [ListenTracker,
SessionTracker,
- Pre_1_3SessionTracker
+ Pre_1_3SessionTracker,
+ Pre_1_3UpgradeSessionTracker
]}}.
@@ -86,3 +88,12 @@ ssl_server_session_child_spec() ->
Modules = [ssl_server_session_cache_sup],
Type = supervisor,
{Name, StartFunc, Restart, Shutdown, Type, Modules}.
+
+ssl_upgrade_server_session_child_spec() ->
+ Name = ssl_upgrade_server_session_cache_sup,
+ StartFunc = {ssl_upgrade_server_session_cache_sup, start_link, []},
+ Restart = permanent,
+ Shutdown = 4000,
+ Modules = [ssl_upgrade_server_session_cache_sup],
+ Type = supervisor,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
diff --git a/lib/ssl/src/tls_socket.erl b/lib/ssl/src/tls_socket.erl
index e2ec4e2f0a..48f1935e81 100644
--- a/lib/ssl/src/tls_socket.erl
+++ b/lib/ssl/src/tls_socket.erl
@@ -49,7 +49,7 @@
start_link/3,
terminate/2,
inherit_tracker/3,
- session_id_tracker/1,
+ session_id_tracker/2,
emulated_socket_options/2,
get_emulated_opts/1,
set_emulated_opts/2,
@@ -63,7 +63,7 @@
-record(state, {
emulated_opts,
- port,
+ listen_monitor,
ssl_opts
}).
@@ -84,7 +84,7 @@ listen(Transport, Port, #config{transport_info = {Transport, _, _, _, _},
%% TLS-1.3 session handling
{ok, SessionHandler} = session_tickets_tracker(LifeTime, TicketStoreSize, SslOpts),
%% PRE TLS-1.3 session handling
- {ok, SessionIdHandle} = session_id_tracker(SslOpts),
+ {ok, SessionIdHandle} = session_id_tracker(ListenSocket, SslOpts),
Trackers = [{option_tracker, Tracker}, {session_tickets_tracker, SessionHandler},
{session_id_tracker, SessionIdHandle}],
Socket = #sslsocket{pid = {ListenSocket, Config#config{trackers = Trackers}}},
@@ -108,7 +108,7 @@ accept(ListenSocket, #config{transport_info = {Transport,_,_,_,_} = CbInfo,
{SslOpts, emulated_socket_options(EmOpts, #socket_options{}), Trackers}, self(), CbInfo],
case tls_connection_sup:start_child(ConnArgs) of
{ok, Pid} ->
- ssl_connection:socket_control(ConnectionCb, Socket, [Pid, Sender], Transport, Trackers);
+ ssl_gen_statem:socket_control(ConnectionCb, Socket, [Pid, Sender], Transport, Trackers);
{error, Reason} ->
{error, Reason}
end;
@@ -122,7 +122,7 @@ upgrade(Socket, #config{transport_info = {Transport,_,_,_,_}= CbInfo,
ok = setopts(Transport, Socket, tls_socket:internal_inet_values()),
case peername(Transport, Socket) of
{ok, {Address, Port}} ->
- ssl_connection:connect(ConnectionCb, Address, Port, Socket,
+ ssl_gen_statem:connect(ConnectionCb, Address, Port, Socket,
{SslOptions,
emulated_socket_options(EmOpts, #socket_options{}), undefined},
self(), CbInfo, Timeout);
@@ -137,7 +137,7 @@ connect(Address, Port,
{Transport, _, _, _, _} = CbInfo,
try Transport:connect(Address, Port, SocketOpts, Timeout) of
{ok, Socket} ->
- ssl_connection:connect(ConnetionCb, Address, Port, Socket,
+ ssl_gen_statem:connect(ConnetionCb, Address, Port, Socket,
{SslOpts,
emulated_socket_options(EmOpts, #socket_options{}), undefined},
self(), CbInfo, Timeout);
@@ -264,24 +264,37 @@ inherit_tracker(ListenSocket, EmOpts, #{erl_dist := true} = SslOpts) ->
session_tickets_tracker(_, _, #{erl_dist := false,
session_tickets := disabled}) ->
{ok, disabled};
-session_tickets_tracker(Lifetime, TicketStoreSize, #{erl_dist := false,
- session_tickets := Mode,
- anti_replay := AntiReplay}) ->
+session_tickets_tracker(Lifetime, TicketStoreSize,
+ #{erl_dist := false,
+ session_tickets := Mode,
+ anti_replay := AntiReplay}) ->
tls_server_session_ticket_sup:start_child([Mode, Lifetime, TicketStoreSize, AntiReplay]);
-session_tickets_tracker(Lifetime, TicketStoreSize, #{erl_dist := true,
- session_tickets := Mode}) ->
- tls_server_session_ticket_sup:start_child_dist([Mode, Lifetime, TicketStoreSize]).
-
-session_id_tracker(#{versions := [{3,4}]}) ->
+session_tickets_tracker(Lifetime, TicketStoreSize,
+ #{erl_dist := true,
+ session_tickets := Mode,
+ anti_replay := AntiReplay}) ->
+ SupName = tls_server_session_ticket_sup:sup_name(dist),
+ Children = supervisor:count_children(SupName),
+ Workers = proplists:get_value(workers, Children),
+ case Workers of
+ 0 ->
+ tls_server_session_ticket_sup:start_child([Mode, Lifetime, TicketStoreSize, AntiReplay]);
+ 1 ->
+ [{_,Child,_, _}] = supervisor:which_children(SupName),
+ {ok, Child}
+ end.
+session_id_tracker(_, #{versions := [{3,4}]}) ->
{ok, not_relevant};
%% Regardless of the option reuse_sessions we need the session_id_tracker
%% to generate session ids, but no sessions will be stored unless
%% reuse_sessions = true.
-session_id_tracker(#{erl_dist := false}) ->
- ssl_server_session_cache_sup:start_child(ssl_server_session_cache_sup:session_opts());
-session_id_tracker(#{erl_dist := true}) ->
- ssl_server_session_cache_sup:start_child_dist(ssl_server_session_cache_sup:session_opts()).
-
+session_id_tracker(ssl_unknown_listener, #{erl_dist := false}) ->
+ ssl_upgrade_server_session_cache_sup:start_child(normal);
+session_id_tracker(ListenSocket, #{erl_dist := false}) ->
+ ssl_server_session_cache_sup:start_child(ListenSocket);
+session_id_tracker(_, #{erl_dist := true}) ->
+ ssl_upgrade_server_session_cache_sup:start_child(dist).
+
get_emulated_opts(TrackerPid) ->
call(TrackerPid, get_emulated_opts).
set_emulated_opts(TrackerPid, InetValues) ->
@@ -303,10 +316,12 @@ start_link(Port, SockOpts, SslOpts) ->
%%
%% Description: Initiates the server
%%--------------------------------------------------------------------
-init([Port, Opts, SslOpts]) ->
+init([Listen, Opts, SslOpts]) ->
process_flag(trap_exit, true),
- true = link(Port),
- {ok, #state{emulated_opts = do_set_emulated_opts(Opts, []), port = Port, ssl_opts = SslOpts}}.
+ Monitor = monitor_listen(Listen),
+ {ok, #state{emulated_opts = do_set_emulated_opts(Opts, []),
+ listen_monitor = Monitor,
+ ssl_opts = SslOpts}}.
%%--------------------------------------------------------------------
-spec handle_call(msg(), from(), #state{}) -> {reply, reply(), #state{}}.
@@ -351,7 +366,7 @@ handle_cast(_, State)->
%%
%% Description: Handling all non call/cast messages
%%-------------------------------------------------------------------
-handle_info({'EXIT', Port, _}, #state{port = Port} = State) ->
+handle_info({'DOWN', Monitor, _, _, _}, #state{listen_monitor = Monitor} = State) ->
{stop, normal, State}.
@@ -380,6 +395,9 @@ code_change(_OldVsn, State, _Extra) ->
call(Pid, Msg) ->
gen_server:call(Pid, Msg, infinity).
+monitor_listen(Listen) when is_port(Listen) ->
+ erlang:monitor(port, Listen).
+
split_options(Opts) ->
split_options(Opts, emulated_options(), [], []).
split_options([], _, SocketOpts, EmuOpts) ->
diff --git a/lib/ssl/src/tls_sup.erl b/lib/ssl/src/tls_sup.erl
index 25c1db0272..a425ae31e2 100644
--- a/lib/ssl/src/tls_sup.erl
+++ b/lib/ssl/src/tls_sup.erl
@@ -45,10 +45,10 @@ start_link() ->
init([]) ->
- TLSConnetionManager = tls_connection_manager_child_spec(),
+ TLSConnetionSup = tls_connection_child_spec(),
ServerInstanceSup = server_instance_child_spec(),
- {ok, {{one_for_one, 10, 3600}, [TLSConnetionManager,
+ {ok, {{one_for_one, 10, 3600}, [TLSConnetionSup,
ServerInstanceSup
]}}.
@@ -57,7 +57,7 @@ init([]) ->
%%% Internal functions
%%--------------------------------------------------------------------
-tls_connection_manager_child_spec() ->
+tls_connection_child_spec() ->
Name = tls_connection,
StartFunc = {tls_connection_sup, start_link, []},
Restart = permanent,
diff --git a/lib/ssl/src/tls_v1.erl b/lib/ssl/src/tls_v1.erl
index 8e6807d0ab..cbba413ee2 100644
--- a/lib/ssl/src/tls_v1.erl
+++ b/lib/ssl/src/tls_v1.erl
@@ -486,21 +486,7 @@ mac_hash(Method, Mac_write_secret, Seq_num, Type, {Major, Minor},
-spec suites(1|2|3|4) -> [ssl_cipher_format:cipher_suite()].
suites(Minor) when Minor == 1; Minor == 2 ->
- [
- ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
- ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
- ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
- ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA,
- ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
- ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,
-
- ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
- ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
- ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
- ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
- ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
- ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA
- ];
+ exclusive_suites(2);
suites(3) ->
exclusive_suites(3) ++ suites(2);
@@ -518,36 +504,42 @@ exclusive_suites(3) ->
[?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+ ?TLS_ECDHE_ECDSA_WITH_AES_256_CCM,
+ ?TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8,
+
?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
+ ?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
+ ?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
+
+ ?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+ ?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+
+ ?TLS_ECDHE_ECDSA_WITH_AES_128_CCM,
+ ?TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8,
+
?TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384,
?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384,
?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384,
?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384,
- ?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,
- ?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384,
-
- ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
- ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256,
-
- ?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
- ?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
-
- ?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
- ?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
+ ?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256,
+ ?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256,
?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
- ?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256,
- ?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256,
-
?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256,
?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256,
+ ?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,
+ ?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384,
+
+ ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
+ ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256,
+
?TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
?TLS_DHE_DSS_WITH_AES_128_GCM_SHA256,
@@ -564,19 +556,19 @@ exclusive_suites(3) ->
];
exclusive_suites(Minor) when Minor == 1; Minor == 2 ->
[
- ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
- ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
- ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
- ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA,
- ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
- ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,
-
- ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
- ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
- ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
- ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
- ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
- ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA
+ ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
+ ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+ ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
+ ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA,
+ ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
+ ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,
+
+ ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+ ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+ ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
+ ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
+ ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
+ ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA
].
signature_algs({3, 4}, HashSigns) ->
diff --git a/lib/ssl/test/dtls_api_SUITE.erl b/lib/ssl/test/dtls_api_SUITE.erl
index 85a31a19be..eb7a16e0f1 100644
--- a/lib/ssl/test/dtls_api_SUITE.erl
+++ b/lib/ssl/test/dtls_api_SUITE.erl
@@ -37,7 +37,19 @@
dtls_listen_close/0,
dtls_listen_close/1,
dtls_listen_reopen/0,
- dtls_listen_reopen/1
+ dtls_listen_reopen/1,
+ dtls_listen_two_sockets_1/0,
+ dtls_listen_two_sockets_1/1,
+ dtls_listen_two_sockets_2/0,
+ dtls_listen_two_sockets_2/1,
+ dtls_listen_two_sockets_3/0,
+ dtls_listen_two_sockets_3/1,
+ dtls_listen_two_sockets_4/0,
+ dtls_listen_two_sockets_4/1,
+ dtls_listen_two_sockets_5/0,
+ dtls_listen_two_sockets_5/1,
+ dtls_listen_two_sockets_6/0,
+ dtls_listen_two_sockets_6/1
]).
%%--------------------------------------------------------------------
@@ -59,7 +71,13 @@ api_tests() ->
[
dtls_listen_owner_dies,
dtls_listen_close,
- dtls_listen_reopen
+ dtls_listen_reopen,
+ dtls_listen_two_sockets_1,
+ dtls_listen_two_sockets_2,
+ dtls_listen_two_sockets_3,
+ dtls_listen_two_sockets_4,
+ dtls_listen_two_sockets_5,
+ dtls_listen_two_sockets_6
].
init_per_suite(Config0) ->
@@ -84,6 +102,20 @@ init_per_group(GroupName, Config) ->
end_per_group(GroupName, Config) ->
ssl_test_lib:end_per_group(GroupName, Config).
+init_per_testcase(Testcase, Config)
+ when Testcase =:= dtls_listen_two_sockets_1 orelse
+ Testcase =:= dtls_listen_two_sockets_5 orelse
+ Testcase =:= dtls_listen_two_sockets_6 ->
+ case ssl:listen(0, [{protocol, dtls}, {ip, {127,0,0,2}}]) of
+ {ok, S} ->
+ test_listen_on_all_interfaces(S, Config),
+ ssl:close(S),
+ ssl_test_lib:ct_log_supported_protocol_versions(Config),
+ ct:timetrap({seconds, 10}),
+ maybe_skip_tc_on_windows(Testcase, Config);
+ {error, _} ->
+ {skip, "127.0.0.x address not available"}
+ end;
init_per_testcase(_TestCase, Config) ->
ssl_test_lib:ct_log_supported_protocol_versions(Config),
ct:timetrap({seconds, 10}),
@@ -188,7 +220,107 @@ dtls_listen_reopen(Config) when is_list(Config) ->
{ssl, Client2, "from server 2"} ->
ssl:close(Client2)
end.
+
+dtls_listen_two_sockets_1() ->
+ [{doc, "Test with two DTLS dockets: 127.0.0.2:Port, 127.0.0.3:Port"}].
+dtls_listen_two_sockets_1(_Config) when is_list(_Config) ->
+ {ok, S1} = ssl:listen(0, [{protocol, dtls}, {ip, {127,0,0,2}}]),
+ {ok, {_, Port}} = ssl:sockname(S1),
+ {ok, S2} = ssl:listen(Port, [{protocol, dtls}, {ip, {127,0,0,3}}]),
+ ssl:close(S1),
+ ssl:close(S2),
+ ok.
+
+dtls_listen_two_sockets_2() ->
+ [{doc, "Test with two DTLS dockets: <all_interfaces>:Port, <all_interfaces>:Port"}].
+dtls_listen_two_sockets_2(_Config) when is_list(_Config) ->
+ {ok, S1} = ssl:listen(0, [{protocol, dtls}]),
+ {ok, {_, Port}} = ssl:sockname(S1),
+ {error, already_listening} =
+ ssl:listen(Port, [{protocol, dtls}]),
+ ssl:close(S1),
+ ok.
+
+dtls_listen_two_sockets_3() ->
+ [{doc, "Test with two DTLS dockets: <all_interfaces>:Port, <all_interfaces>:Port"}].
+dtls_listen_two_sockets_3(_Config) when is_list(_Config) ->
+ {ok, S1} = ssl:listen(0, [{protocol, dtls}]),
+ {ok, {_, Port}} = ssl:sockname(S1),
+ {error, already_listening} =
+ ssl:listen(Port, [{protocol, dtls}]),
+ ssl:close(S1),
+ {ok, S2} = ssl:listen(Port, [{protocol, dtls}]),
+ ssl:close(S2),
+ ok.
+
+dtls_listen_two_sockets_4() ->
+ [{doc, "Test with two DTLS dockets: process1 - <all_interfaces>:Port, process2 - <all_interfaces>:Port"}].
+dtls_listen_two_sockets_4(_Config) when is_list(_Config) ->
+ Test = self(),
+ Pid = spawn(fun() ->
+ {ok, S1} = ssl:listen(0, [{protocol, dtls}]),
+ {ok, {_, Port0}} = ssl:sockname(S1),
+ Test ! {self(), Port0}
+ end),
+ Port =
+ receive
+ {Pid, Port1} ->
+ Port1
+ end,
+ {ok, S2} =
+ ssl:listen(Port, [{protocol, dtls}]),
+ ssl:close(S2),
+ ok.
+
+dtls_listen_two_sockets_5() ->
+ [{doc, "Test with two DTLS dockets: <all_interfaces>:Port, 127.0.0.3:Port"}].
+dtls_listen_two_sockets_5(_Config) when is_list(_Config) ->
+ {ok, S1} = ssl:listen(0, [{protocol, dtls}]),
+ {ok, {_, Port}} = ssl:sockname(S1),
+ {error, already_listening} =
+ ssl:listen(Port, [{protocol, dtls}, {ip, {127,0,0,3}}]),
+ ssl:close(S1),
+ {ok, S2} =
+ ssl:listen(Port, [{protocol, dtls}, {ip, {127,0,0,3}}]),
+ {error, already_listening} =
+ ssl:listen(Port, [{protocol, dtls}]),
+ ssl:close(S2),
+ ok.
+
+dtls_listen_two_sockets_6() ->
+ [{doc, "Test with two DTLS dockets: 127.0.0.3:Port, 0.0.0.0:Port"}].
+dtls_listen_two_sockets_6(_Config) when is_list(_Config) ->
+ {ok, S1} = ssl:listen(0, [{protocol, dtls}, {ip, {127,0,0,3}}]),
+ {ok, {_, Port}} = ssl:sockname(S1),
+ {error, already_listening} =
+ ssl:listen(Port, [{protocol, dtls}, {ip, {0,0,0,0}}]),
+ ssl:close(S1),
+ ok.
+
%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------
+%% Helper function for init_per_testcase.
+test_listen_on_all_interfaces(S0, Config) ->
+ {ok, {_, Port}} = ssl:sockname(S0),
+ case ssl:listen(Port, [{protocol, dtls}, {ip, {0,0,0,0}}]) of
+ {ok, S1} ->
+ ssl:close(S0),
+ ssl:close(S1),
+ {skip, "Testcase is not supported on this OS."};
+ {error, _} ->
+ Config
+ end.
+
+maybe_skip_tc_on_windows(Testcase, Config)
+ when Testcase =:= dtls_listen_two_sockets_5 orelse
+ Testcase =:= dtls_listen_two_sockets_6 ->
+ case os:type() of
+ {win32, _} ->
+ {skip, "Testcase not supported in Windows"};
+ _ ->
+ Config
+ end;
+maybe_skip_tc_on_windows(_, Config) ->
+ Config.
diff --git a/lib/ssl/test/openssl_cipher_suite_SUITE.erl b/lib/ssl/test/openssl_cipher_suite_SUITE.erl
index 6d8147a3df..eb823471c8 100644
--- a/lib/ssl/test/openssl_cipher_suite_SUITE.erl
+++ b/lib/ssl/test/openssl_cipher_suite_SUITE.erl
@@ -82,10 +82,14 @@
aes_128_gcm_sha256/1,
chacha20_poly1305_sha256/1,
aes_128_ccm_sha256/1,
- aes_128_ccm_8_sha256/1
+ aes_128_ccm_8_sha256/1,
+ ecdhe_ecdsa_with_aes_128_ccm/1,
+ ecdhe_ecdsa_with_aes_256_ccm/1,
+ ecdhe_ecdsa_with_aes_128_ccm_8/1,
+ ecdhe_ecdsa_with_aes_256_ccm_8/1
]).
--define(DEFAULT_TIMEOUT, {seconds, 6}).
+-define(DEFAULT_TIMEOUT, {seconds, 10}).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
@@ -140,7 +144,11 @@ groups() ->
ecdhe_ecdsa_aes_128_gcm,
ecdhe_ecdsa_aes_256_cbc,
ecdhe_ecdsa_aes_256_gcm,
- ecdhe_ecdsa_chacha20_poly1305
+ ecdhe_ecdsa_chacha20_poly1305,
+ ecdhe_ecdsa_with_aes_128_ccm,
+ ecdhe_ecdsa_with_aes_256_ccm,
+ ecdhe_ecdsa_with_aes_128_ccm_8,
+ ecdhe_ecdsa_with_aes_256_ccm_8
]},
{rsa, [], [rsa_des_cbc,
rsa_3des_ede_cbc,
@@ -298,7 +306,8 @@ do_init_per_group(ecdhe_ecdsa = GroupName, Config) ->
end;
do_init_per_group(dhe_dss = GroupName, Config) ->
PKAlg = proplists:get_value(public_keys, crypto:supports()),
- case lists:member(dss, PKAlg) andalso lists:member(dh, PKAlg) of
+ case lists:member(dss, PKAlg) andalso lists:member(dh, PKAlg)
+ andalso (ssl_test_lib:openssl_dsa_suites() =/= []) of
true ->
init_certs(GroupName, Config);
false ->
@@ -306,7 +315,8 @@ do_init_per_group(dhe_dss = GroupName, Config) ->
end;
do_init_per_group(srp_dss = GroupName, Config) ->
PKAlg = proplists:get_value(public_keys, crypto:supports()),
- case lists:member(dss, PKAlg) andalso lists:member(srp, PKAlg) of
+ case lists:member(dss, PKAlg) andalso lists:member(srp, PKAlg)
+ andalso (ssl_test_lib:openssl_dsa_suites() =/= []) of
true ->
init_certs(GroupName, Config);
false ->
@@ -339,11 +349,11 @@ do_init_per_group(dhe_rsa = GroupName, Config) ->
end;
do_init_per_group(rsa = GroupName, Config) ->
PKAlg = proplists:get_value(public_keys, crypto:supports()),
- case lists:member(rsa, PKAlg) of
+ case lists:member(rsa, PKAlg) andalso ssl_test_lib:openssl_support_rsa_kex() of
true ->
init_certs(GroupName, Config);
false ->
- {skip, "Missing SRP crypto support"}
+ {skip, "Missing RSA key exchange support"}
end;
do_init_per_group(dh_anon = GroupName, Config) ->
PKAlg = proplists:get_value(public_keys, crypto:supports()),
@@ -375,7 +385,7 @@ init_per_testcase(TestCase, Config) when TestCase == psk_3des_ede_cbc;
SupCiphers = proplists:get_value(ciphers, crypto:supports()),
case lists:member(des_ede3, SupCiphers) of
true ->
- ct:timetrap({seconds, 5}),
+ ct:timetrap({seconds, ?DEFAULT_TIMEOUT}),
Config;
_ ->
{skip, "Missing 3DES crypto support"}
@@ -478,6 +488,28 @@ init_per_testcase(aes_128_ccm_8_sha256, Config) ->
{skip, "Missing AES_128_CCM_8 crypto support"}
end;
+init_per_testcase(TestCase, Config) when TestCase == ecdhe_ecdsa_with_aes_128_ccm;
+ TestCase == ecdhe_ecdsa_with_aes_128_ccm_8->
+ SupCiphers = proplists:get_value(ciphers, crypto:supports()),
+ case lists:member(aes_128_ccm, SupCiphers) of
+ true ->
+ ct:timetrap(?DEFAULT_TIMEOUT),
+ Config;
+ _ ->
+ {skip, "Missing AES_128_CCM crypto support"}
+ end;
+
+init_per_testcase(TestCase, Config) when TestCase == ecdhe_ecdsa_with_aes_256_ccm;
+ TestCase == ecdhe_ecdsa_with_aes_256_ccm_8 ->
+ SupCiphers = proplists:get_value(ciphers, crypto:supports()),
+ case lists:member(aes_256_ccm, SupCiphers) of
+ true ->
+ ct:timetrap(?DEFAULT_TIMEOUT),
+ Config;
+ _ ->
+ {skip, "Missing AES_256_CCM crypto support"}
+ end;
+
init_per_testcase(TestCase, Config) ->
Cipher = ssl_test_lib:test_cipher(TestCase, Config),
SupCiphers = proplists:get_value(ciphers, crypto:supports()),
@@ -728,6 +760,18 @@ ecdhe_ecdsa_aes_256_gcm(Config) when is_list(Config) ->
ecdhe_ecdsa_chacha20_poly1305(Config) when is_list(Config) ->
run_ciphers_test(ecdhe_ecdsa, 'chacha20_poly1305', Config).
+
+ecdhe_ecdsa_with_aes_128_ccm(Config) when is_list(Config) ->
+ run_ciphers_test(ecdhe_ecdsa, 'aes_128_ccm', Config).
+
+ecdhe_ecdsa_with_aes_256_ccm(Config) when is_list(Config) ->
+ run_ciphers_test(ecdhe_ecdsa, 'aes_256_ccm', Config).
+
+ecdhe_ecdsa_with_aes_128_ccm_8(Config) when is_list(Config) ->
+ run_ciphers_test(ecdhe_ecdsa, 'aes_128_ccm_8', Config).
+
+ecdhe_ecdsa_with_aes_256_ccm_8(Config) when is_list(Config) ->
+ run_ciphers_test(ecdhe_ecdsa, 'aes_256_ccm_8', Config).
%%--------------------------------------------------------------------
%% DHE_DSS --------------------------------------------------------
%%--------------------------------------------------------------------
@@ -897,7 +941,7 @@ run_ciphers_test(Kex, Cipher, Config) ->
{skip, {not_sup, Kex, Cipher, Version}}
end.
-cipher_suite_test(CipherSuite, _Version, Config) ->
+cipher_suite_test(CipherSuite, Version, Config) ->
#{server_config := SOpts,
client_config := COpts} = proplists:get_value(tls_config, Config),
ServerOpts = ssl_test_lib:ssl_options(SOpts, Config),
@@ -905,11 +949,17 @@ cipher_suite_test(CipherSuite, _Version, Config) ->
ct:log("Testing CipherSuite ~p~n", [CipherSuite]),
ct:log("Server Opts ~p~n", [ServerOpts]),
ct:log("Client Opts ~p~n", [ClientOpts]),
- ssl_test_lib:basic_test([{ciphers, [CipherSuite]} | COpts], SOpts, Config).
-
+ case proplists:get_value(server_type, Config) of
+ erlang ->
+ ssl_test_lib:basic_test([{ciphers, ssl:cipher_suites(all, Version)} | COpts],
+ [{ciphers, [CipherSuite]} | SOpts], Config);
+ _ ->
+ ssl_test_lib:basic_test([{versions, [Version]}, {ciphers, [CipherSuite]} | COpts],
+ [{ciphers, ssl:cipher_suites(all, Version)} | SOpts], Config)
+ end.
test_ciphers(Kex, Cipher, Version) ->
- Ciphers = ssl:filter_cipher_suites(ssl:cipher_suites(default, Version) ++ ssl:cipher_suites(anonymous, Version),
+ Ciphers = ssl:filter_cipher_suites(ssl:cipher_suites(all, Version) ++ ssl:cipher_suites(anonymous, Version),
[{key_exchange,
fun(Kex0) when (Kex0 == Kex) andalso (Version =/= 'tlsv1.3') -> true;
(Kex0) when (Kex0 == any) andalso (Version == 'tlsv1.3') -> true;
@@ -920,7 +970,7 @@ test_ciphers(Kex, Cipher, Version) ->
(_) -> false
end}]),
ct:log("Version ~p Testing ~p~n", [Version, Ciphers]),
- OpenSSLCiphers = openssl_ciphers(),
+ OpenSSLCiphers = ssl_test_lib:openssl_ciphers(),
ct:log("OpenSSLCiphers ~p~n", [OpenSSLCiphers]),
lists:filter(fun(C) ->
ct:log("Cipher ~p~n", [C]),
@@ -928,6 +978,3 @@ test_ciphers(Kex, Cipher, Version) ->
end, Ciphers).
-openssl_ciphers() ->
- Str = os:cmd("openssl ciphers"),
- string:split(string:strip(Str, right, $\n), ":", all).
diff --git a/lib/ssl/test/openssl_client_cert_SUITE.erl b/lib/ssl/test/openssl_client_cert_SUITE.erl
index 7adfdf32a5..7e8d842f14 100644
--- a/lib/ssl/test/openssl_client_cert_SUITE.erl
+++ b/lib/ssl/test/openssl_client_cert_SUITE.erl
@@ -292,7 +292,8 @@ end_per_group(GroupName, Config) ->
init_per_testcase(TestCase, Config) when
TestCase == client_auth_empty_cert_accepted;
TestCase == client_auth_empty_cert_rejected ->
- Version = proplists:get_value(version,Config),
+ Version = ssl_test_lib:protocol_version(Config),
+
case Version of
sslv3 ->
%% Openssl client sends "No Certificate Reserved" warning ALERT
diff --git a/lib/ssl/test/openssl_server_cert_SUITE.erl b/lib/ssl/test/openssl_server_cert_SUITE.erl
index 4402765ea2..e71bfc8e5c 100644
--- a/lib/ssl/test/openssl_server_cert_SUITE.erl
+++ b/lib/ssl/test/openssl_server_cert_SUITE.erl
@@ -275,7 +275,7 @@ init_per_group(ecdsa_1_3 = Group, Config0) ->
COpts = proplists:get_value(client_ecdsa_opts, Config),
SOpts = proplists:get_value(server_ecdsa_opts, Config),
%% Make sure ecdh* suite is choosen by ssl_test_lib:start_server
- Version = proplists:get_value(version,Config),
+ Version = ssl_test_lib:protocol_version(Config),
Ciphers = ssl_cert_tests:test_ciphers(undefined, Version),
case Ciphers of
[_|_] ->
@@ -301,7 +301,7 @@ init_per_group(Group, Config0) when Group == dsa ->
COpts = proplists:get_value(client_dsa_opts, Config),
SOpts = proplists:get_value(server_dsa_opts, Config),
%% Make sure dhe_dss* suite is choosen by ssl_test_lib:start_server
- Version = proplists:get_value(version,Config),
+ Version = ssl_test_lib:protocol_version(Config),
Ciphers = ssl_cert_tests:test_ciphers(fun(dh_dss) ->
true;
(dhe_dss) ->
diff --git a/lib/ssl/test/openssl_session_SUITE.erl b/lib/ssl/test/openssl_session_SUITE.erl
index 0fdc7d6dab..08369733dc 100644
--- a/lib/ssl/test/openssl_session_SUITE.erl
+++ b/lib/ssl/test/openssl_session_SUITE.erl
@@ -95,7 +95,11 @@ init_per_suite(Config0) ->
try crypto:start() of
ok ->
ssl_test_lib:clean_start(),
- ssl_test_lib:make_rsa_cert(Config0)
+ {ClientOpts, ServerOpts} =
+ ssl_test_lib:make_rsa_cert_chains([{server_chain, ssl_test_lib:default_cert_chain_conf()},
+ {client_chain, ssl_test_lib:default_cert_chain_conf()}],
+ Config0, "openssl_session_SUITE"),
+ [{client_opts, ClientOpts}, {server_opts, ServerOpts} | Config0]
catch _:_ ->
{skip, "Crypto did not start"}
end
@@ -120,19 +124,24 @@ init_per_testcase(reuse_session_erlang_client, Config) ->
ssl:start(),
Config;
init_per_testcase(reuse_session_erlang_server, Config) ->
- Version = ssl_test_lib:protocol_version(Config),
- case ssl_test_lib:is_dtls_version(Version) of
+ case ssl_test_lib:working_openssl_client() of
true ->
- case ssl_test_lib:openssl_sane_dtls_session_reuse() of
+ Version = ssl_test_lib:protocol_version(Config),
+ case ssl_test_lib:is_dtls_version(Version) of
true ->
- ct:timetrap(?TIMEOUT),
- Config;
+ case ssl_test_lib:openssl_sane_dtls_session_reuse() of
+ true ->
+ ct:timetrap(?TIMEOUT),
+ Config;
+ false ->
+ {skip, "Broken OpenSSL DTLS session reuse"}
+ end;
false ->
- {skip, "Broken OpenSSL DTLS session reuse"}
+ ct:timetrap(?TIMEOUT),
+ Config
end;
- false ->
- ct:timetrap(?TIMEOUT),
- Config
+ false ->
+ {skip, "Broken OpenSSL s_client"}
end;
init_per_testcase(_TestCase, Config) ->
ct:timetrap(?TIMEOUT),
@@ -153,24 +162,28 @@ reuse_session_erlang_server() ->
[{doc, "Test erlang server with openssl client that reconnects with the"
"same session id, to test reusing of sessions."}].
reuse_session_erlang_server(Config) when is_list(Config) ->
- ClientOpts = proplists:get_value(client_rsa_opts, Config),
- ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ ClientOpts = proplists:get_value(client_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
+ Version = ssl_test_lib:protocol_version(Config),
{_, ServerNode, _} = ssl_test_lib:run_where(Config),
+ Ciphers = common_ciphers(Version),
+
Data = "From openssl to erlang",
Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
{from, self()},
{mfa, {ssl_test_lib, active_recv, [length(Data)]}},
{reconnect_times, 5},
- {options, ServerOpts}]),
+ {options, [{ciphers, Ciphers},
+ {versions, [Version]}| ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
{_Client, OpenSSLPort} = ssl_test_lib:start_client(openssl, [{port, Port},
{reconnect, true},
- {options, ClientOpts},
+ {options, [{ciphers, Ciphers} | ClientOpts]},
return_port], Config),
true = port_command(OpenSSLPort, Data),
@@ -182,12 +195,14 @@ reuse_session_erlang_server(Config) when is_list(Config) ->
reuse_session_erlang_client() ->
[{doc, "Test erlang ssl client that wants to reuse sessions"}].
reuse_session_erlang_client(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
- ServerOpts = proplists:get_value(server_rsa_opts, Config),
- {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
+ ServerOpts = proplists:get_value(server_opts, Config),
+ Version = ssl_test_lib:protocol_version(Config),
+ {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
+ Ciphers = common_ciphers(Version),
Server = ssl_test_lib:start_server(openssl, [],
- [{server_opts, ServerOpts} | Config]),
+ [{server_opts, [{ciphers, Ciphers} | ServerOpts]} | Config]),
Port = ssl_test_lib:inet_port(Server),
Client0 =
@@ -196,7 +211,9 @@ reuse_session_erlang_client(Config) when is_list(Config) ->
{mfa, {ssl_test_lib, session_id, []}},
{from, self()},
{options, [{reuse_sessions, save},
- {verify, verify_peer}| ClientOpts]}]),
+ {verify, verify_peer},
+ {ciphers, Ciphers},
+ {versions, [Version]} | ClientOpts]}]),
SID = receive
{Client0, Id0} ->
@@ -209,7 +226,9 @@ reuse_session_erlang_client(Config) when is_list(Config) ->
ssl_test_lib:start_client([{node, ClientNode},
{port, Port}, {host, Hostname},
{mfa, {ssl_test_lib, session_id, []}},
- {from, self()}, {options, [{reuse_session, SID} | ClientOpts]}]),
+ {from, self()}, {options, [ {ciphers, Ciphers},
+ {versions, [Version]},
+ {reuse_session, SID} | ClientOpts]}]),
receive
{Client1, SID} ->
ok
@@ -226,7 +245,8 @@ reuse_session_erlang_client(Config) when is_list(Config) ->
ssl_test_lib:start_client([{node, ClientNode},
{port, Port}, {host, Hostname},
{mfa, {ssl_test_lib, session_id, []}},
- {from, self()}, {options, ClientOpts}]),
+ {from, self()}, {options, [{ciphers, Ciphers},
+ {versions, [Version]} | ClientOpts]}]),
receive
{Client2, ID} ->
case ID of
@@ -242,3 +262,10 @@ reuse_session_erlang_client(Config) when is_list(Config) ->
%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------
+
+common_ciphers(Version) ->
+ OpenSSLCiphers = ssl_test_lib:openssl_ciphers(),
+ ErlOpenSSLCiphers = [ssl:str_to_suite(C) || C <- OpenSSLCiphers],
+ ErlCiphers = ssl:cipher_suites(all, Version),
+ [Suite || Suite <- ErlOpenSSLCiphers, lists:member(Suite, ErlCiphers)].
+
diff --git a/lib/ssl/test/property_test/ssl_eqc_chain.erl b/lib/ssl/test/property_test/ssl_eqc_chain.erl
new file mode 100644
index 0000000000..e78dc3fc0e
--- /dev/null
+++ b/lib/ssl/test/property_test/ssl_eqc_chain.erl
@@ -0,0 +1,411 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2020. 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(ssl_eqc_chain).
+
+%%-export([prop_tls_orded_path/1]).
+-compile(export_all).
+
+-proptest(eqc).
+-proptest([triq,proper]).
+
+-ifndef(EQC).
+-ifndef(PROPER).
+-ifndef(TRIQ).
+-define(EQC,true).
+-endif.
+-endif.
+-endif.
+
+-ifdef(EQC).
+-include_lib("eqc/include/eqc.hrl").
+-define(MOD_eqc,eqc).
+
+-else.
+-ifdef(PROPER).
+-include_lib("proper/include/proper.hrl").
+-define(MOD_eqc,proper).
+
+-else.
+-ifdef(TRIQ).
+-define(MOD_eqc,triq).
+-include_lib("triq/include/triq.hrl").
+
+-endif.
+-endif.
+-endif.
+
+-include_lib("public_key/include/public_key.hrl").
+%%--------------------------------------------------------------------
+%% Properties --------------------------------------------------------
+%%--------------------------------------------------------------------
+prop_tls_unordered_path(PrivDir) ->
+ ?FORALL({ClientOptions, ServerOptions}, ?LET(Version, tls_version(), unordered_options(Version, PrivDir)),
+ try
+ [TLSVersion] = proplists:get_value(versions, ClientOptions),
+ ssl_test_lib:basic_test(ClientOptions, ServerOptions, [{server_type, erlang},
+ {client_type, erlang},
+ {version, TLSVersion}
+ ])
+ of
+ _ ->
+ true
+ catch
+ _:_ ->
+ false
+ end
+ ).
+
+prop_tls_extraneous_path(PrivDir) ->
+ ?FORALL({ClientOptions, ServerOptions}, ?LET(Version, tls_version(), extraneous_options(Version, PrivDir)),
+ try
+ [TLSVersion] = proplists:get_value(versions, ClientOptions),
+ ssl_test_lib:basic_test(ClientOptions, ServerOptions, [{server_type, erlang},
+ {client_type, erlang},
+ {version, TLSVersion}
+ ])
+ of
+ _ ->
+ true
+ catch
+ _:_ ->
+ false
+ end
+ ).
+
+prop_tls_extraneous_paths() ->
+ ?FORALL({ClientOptions, ServerOptions}, ?LET(Version, tls_version(), extra_extraneous_options(Version)),
+ try
+ [TLSVersion] = proplists:get_value(versions, ClientOptions),
+ ssl_test_lib:basic_test(ClientOptions, ServerOptions, [{server_type, erlang},
+ {client_type, erlang},
+ {version, TLSVersion}
+ ])
+ of
+ _ ->
+ true
+ catch
+ _:_ ->
+ false
+ end
+ ).
+
+prop_tls_extraneous_and_unordered_path() ->
+ ?FORALL({ClientOptions, ServerOptions}, ?LET(Version, tls_version(), unordered_extraneous_options(Version)),
+ try
+ [TLSVersion] = proplists:get_value(versions, ClientOptions),
+ ssl_test_lib:basic_test(ClientOptions, ServerOptions, [{server_type, erlang},
+ {client_type, erlang},
+ {version, TLSVersion}
+ ])
+ of
+ _ ->
+ true
+ catch
+ _:_ ->
+ false
+ end
+ ).
+
+%%--------------------------------------------------------------------
+%% Chain Generators -----------------------------------------------
+%%--------------------------------------------------------------------
+tls_version() ->
+ Versions = [Version || Version <- ['tlsv1.3', 'tlsv1.2', 'tlsv1.1', 'tlsv1', 'dtlsv1.2', 'dtlsv1'],
+ ssl_test_lib:sufficient_crypto_support(Version)
+ ],
+ oneof(Versions).
+
+key_alg(Version) when Version == 'tlsv1.3';
+ Version == 'tlsv1.2';
+ Version == 'dtlsv1.2'->
+ oneof([rsa, ecdsa]);
+key_alg(_) ->
+ oneof([rsa]).
+
+server_options('tlsv1.3') ->
+ [{verify, verify_peer},
+ {fail_if_no_peer_cert, true},
+ {reuseaddr, true}];
+server_options(_) ->
+ [{verify, verify_peer},
+ {fail_if_no_peer_cert, true},
+ {reuse_sessions, false},
+ {reuseaddr, true}].
+
+client_options(_) ->
+ [{verify, verify_peer}].
+
+unordered_options(Version, PrivDir) ->
+ oneof([der_unordered_options(Version), pem_unordered_options(Version, PrivDir)]).
+
+der_unordered_options(Version) ->
+ ?LET(Alg, key_alg(Version), unordered_der_cert_chain_opts(Version, Alg)).
+
+pem_unordered_options(Version, PrivDir) ->
+ ?LET(Alg, key_alg(Version), unordered_pem_cert_chain_opts(Version, Alg, PrivDir)).
+
+unordered_der_cert_chain_opts(Version, Alg) ->
+ #{server_config := ServerConf,
+ client_config := ClientConf} = public_key:pkix_test_data(#{server_chain => #{root => root_key(Alg),
+ intermediates => intermediates(Alg, 4),
+ peer => peer_key(Alg)},
+ client_chain => #{root => root_key(Alg),
+ intermediates => intermediates(Alg, 4),
+ peer => peer_key(Alg)}}),
+ {client_options(Version) ++ [protocol(Version), {versions, [Version]} | unordered_der_conf(ClientConf)],
+ server_options(Version) ++ [protocol(Version), {versions, [Version]} | unordered_der_conf(ServerConf)]}.
+
+unordered_pem_cert_chain_opts(Version, Alg, PrivDir) ->
+ Index = integer_to_list(erlang:unique_integer()),
+ DerConfig = public_key:pkix_test_data(#{server_chain => #{root => root_key(Alg),
+ intermediates => intermediates(Alg, 4),
+ peer => peer_key(Alg)},
+ client_chain => #{root => root_key(Alg),
+ intermediates => intermediates(Alg, 4),
+ peer => peer_key(Alg)}}),
+
+ ClientBase = filename:join(PrivDir, "client_prop_test" ++ Index),
+ SeverBase = filename:join(PrivDir, "server_prop_test" ++ Index),
+ PemConfig = x509_test:gen_pem_config_files(DerConfig, ClientBase, SeverBase),
+ ClientConf = proplists:get_value(client_config, PemConfig),
+ ServerConf = proplists:get_value(server_config, PemConfig),
+ {client_options(Version) ++ [protocol(Version), {versions, [Version]} | unordered_pem_conf(ClientConf)],
+ server_options(Version) ++ [protocol(Version), {versions, [Version]} | unordered_pem_conf(ServerConf)]}.
+
+unordered_der_conf(Config) ->
+ Cert = proplists:get_value(cert, Config),
+ {ok, ExtractedCAs} = ssl_pkix_db:extract_trusted_certs({der, proplists:get_value(cacerts, Config)}),
+ {ok, _, [Cert | Path]} = ssl_certificate:certificate_chain(Cert, ets:new(foo, []), ExtractedCAs, []),
+ [{cert, [Cert | lists:reverse(Path)]}| proplists:delete(cert, Config)].
+
+unordered_pem_conf(Config) ->
+ CertFile = proplists:get_value(certfile, Config),
+ CACertFile = proplists:get_value(cacertfile, Config),
+ [{_, Cert, _}| _] = ssl_test_lib:pem_to_der(CertFile),
+ PemCAs = ssl_test_lib:pem_to_der(CACertFile),
+ DerList = [DerCert || {'Certificate', DerCert, not_encrypted} <- PemCAs],
+ {ok, ExtractedCAs} = ssl_pkix_db:extract_trusted_certs({der, DerList}),
+ {ok, _, [Cert | Path]} = ssl_certificate:certificate_chain(Cert, ets:new(foo, []), ExtractedCAs, []),
+ Unorded = lists:reverse(Path),
+ UnordedPemEntries = [{'Certificate', DerCert, not_encrypted} || DerCert <- Unorded],
+ PEM = public_key:pem_encode([{'Certificate', Cert, not_encrypted} |UnordedPemEntries]),
+ file:write_file(CertFile, PEM),
+ Config.
+
+extraneous_options(Version, PrivDir) ->
+ oneof([der_extraneous_options(Version),
+ pem_extraneous_options(Version, PrivDir)
+ ]).
+extra_extraneous_options(Version) ->
+ oneof([extra_der_extraneous_options(Version)]).
+
+der_extraneous_options(Version) ->
+ ?LET(Alg, key_alg(Version), extraneous_der_cert_chain_opts(Version, Alg)).
+
+pem_extraneous_options(Version, PrivDir) ->
+ ?LET(Alg, key_alg(Version), extraneous_pem_cert_chain_opts(Version, Alg, PrivDir)).
+
+extra_der_extraneous_options(Version) ->
+ ?LET(Alg, key_alg(Version), extra_extraneous_der_cert_chain_opts(Version, Alg)).
+
+unordered_extraneous_options(Version) ->
+ oneof([der_extraneous_and_unorder_options(Version)]).
+
+der_extraneous_and_unorder_options(Version) ->
+ ?LET(Alg, key_alg(Version), der_extraneous_and_unorder_chain(Version, Alg)).
+
+extraneous_der_cert_chain_opts(Version, Alg) ->
+ #{cert := OrgSRoot} = SRoot = public_key:pkix_test_root_cert("OTP test server ROOT", root_key(Alg)),
+ #{cert := OrgCRoot} = CRoot = public_key:pkix_test_root_cert("OTP test client ROOT", root_key(Alg)),
+
+ #{server_config := ServerConf0,
+ client_config := ClientConf0} = public_key:pkix_test_data(#{server_chain => #{root => SRoot,
+ intermediates => intermediates(Alg, 1),
+ peer => []},
+ client_chain => #{root => CRoot,
+ intermediates => intermediates(Alg, 1),
+ peer => []}}),
+
+ {ClientChain, ClientRoot} = extraneous_chain_and_root(ClientConf0, "OTP test client ROOT", 1),
+ {ServerChain, ServerRoot} = extraneous_chain_and_root(ServerConf0, "OTP test server ROOT", 1),
+
+
+ {client_options(Version) ++ [protocol(Version), {versions, [Version]} |
+ extraneous_der_conf(ClientChain, ServerRoot, [OrgSRoot], ClientConf0)],
+ server_options(Version) ++ [protocol(Version), {versions, [Version]} |
+ extraneous_der_conf(ServerChain, ClientRoot, [OrgCRoot], ServerConf0)]}.
+
+extraneous_pem_cert_chain_opts(Version, Alg, PrivDir) ->
+ #{cert := OrgSRoot} = SRoot = public_key:pkix_test_root_cert("OTP test server ROOT", root_key(Alg)),
+ #{cert := OrgCRoot} = CRoot = public_key:pkix_test_root_cert("OTP test client ROOT", root_key(Alg)),
+
+ #{server_config := ServerConf0,
+ client_config := ClientConf0} = public_key:pkix_test_data(#{server_chain => #{root => SRoot,
+ intermediates => intermediates(Alg, 1),
+ peer => []},
+ client_chain => #{root => CRoot,
+ intermediates => intermediates(Alg, 1),
+ peer => []}}),
+
+ {ClientChain, ClientRoot} = extraneous_chain_and_root(ClientConf0, "OTP test client ROOT", 1),
+ {ServerChain, ServerRoot} = extraneous_chain_and_root(ServerConf0, "OTP test server ROOT", 1),
+
+ %% Only use files in final step
+ {client_options(Version) ++ [protocol(Version), {versions, [Version]} |
+ extraneous_pem_conf(ClientChain, ServerRoot, OrgSRoot, ClientConf0, PrivDir)],
+ server_options(Version) ++ [protocol(Version), {versions, [Version]} |
+ extraneous_pem_conf(ServerChain, ClientRoot, OrgCRoot, ServerConf0, PrivDir)]}.
+
+extra_extraneous_der_cert_chain_opts(Version, Alg) ->
+
+ #{cert := OrgSRoot} = SRoot = public_key:pkix_test_root_cert("OTP test server ROOT", root_key(Alg)),
+ #{cert := OrgCRoot} = CRoot = public_key:pkix_test_root_cert("OTP test client ROOT", root_key(Alg)),
+
+ #{server_config := ServerConf0,
+ client_config := ClientConf0} = public_key:pkix_test_data(#{server_chain => #{root => SRoot,
+ intermediates => intermediates(Alg, 3),
+ peer => []},
+ client_chain => #{root => CRoot,
+ intermediates => intermediates(Alg, 3),
+ peer => []}}),
+
+
+ {ClientChain0, ClientRoot0} = extraneous_chain_and_root(ClientConf0, "OTP test client ROOT", 2),
+ {ServerChain0, ServerRoot0} = extraneous_chain_and_root(ServerConf0, "OTP test server ROOT", 2),
+
+ {ClientChain1, ClientRoot1} = extraneous_chain_and_root(ClientConf0, "OTP test client ROOT", 2),
+ {ServerChain1, ServerRoot1} = extraneous_chain_and_root(ServerConf0, "OTP test server ROOT", 2),
+
+ {ClientChain, ServerChain} = create_extraneous_chains(ClientChain0, ClientChain1,
+ ServerChain0, ServerChain1),
+
+ {client_options(Version) ++ [protocol(Version), {versions, [Version]} |
+ extraneous_der_conf(ClientChain, ServerRoot1, [OrgSRoot, ServerRoot0], ClientConf0)],
+ server_options(Version) ++ [protocol(Version), {versions, [Version]} |
+ extraneous_der_conf(ServerChain, ClientRoot1, [OrgCRoot, ClientRoot0], ServerConf0)]}.
+
+
+der_extraneous_and_unorder_chain(Version, Alg) ->
+
+ #{cert := OrgSRoot} = SRoot = public_key:pkix_test_root_cert("OTP test server ROOT", root_key(Alg)),
+ #{cert := OrgCRoot} = CRoot = public_key:pkix_test_root_cert("OTP test client ROOT", root_key(Alg)),
+
+ #{server_config := ServerConf0,
+ client_config := ClientConf0} =
+ public_key:pkix_test_data(#{server_chain => #{root => SRoot,
+ intermediates => intermediates(Alg, 3),
+ peer => []},
+ client_chain => #{root => CRoot,
+ intermediates => intermediates(Alg, 3),
+ peer => []}}),
+
+ {ClientChain0, ClientRoot0} = chain_and_root(ClientConf0),
+ {ServerChain0, ServerRoot0} = chain_and_root(ServerConf0),
+
+ {ClientChain1, ClientRoot1} = extraneous_chain_and_root(ClientConf0, "OTP test client ROOT", 2),
+ {ServerChain1, ServerRoot1} = extraneous_chain_and_root(ServerConf0, "OTP test server ROOT", 2),
+
+ {ClientChain, ServerChain} = create_extraneous_and_unorded(ClientChain0, ClientChain1,
+ ServerChain0, ServerChain1),
+
+ {client_options(Version) ++ [protocol(Version), {versions, [Version]} |
+ extraneous_der_conf(ClientChain, ServerRoot1, [OrgSRoot, ServerRoot0], ClientConf0)],
+ server_options(Version) ++ [protocol(Version), {versions, [Version]} |
+ extraneous_der_conf(ServerChain, ClientRoot1, [OrgCRoot, ClientRoot0], ServerConf0)]}.
+
+chain_and_root(Config) ->
+ OwnCert = proplists:get_value(cert, Config),
+ {ok, ExtractedCAs} = ssl_pkix_db:extract_trusted_certs({der, proplists:get_value(cacerts, Config)}),
+ {ok, Root, Chain} = ssl_certificate:certificate_chain(OwnCert, ets:new(foo, []), ExtractedCAs, []),
+ {Chain, Root}.
+
+extraneous_chain_and_root(Config, Name, 1) ->
+ #{cert := NewRoot, key := Key} = public_key:pkix_test_root_cert(Name, []),
+ {[OwnCert, CA0, OldRoot], OldRoot} = chain_and_root(Config),
+ CA1 = new_intermediat(CA0, Key),
+ {[OwnCert, CA1, CA0], NewRoot};
+extraneous_chain_and_root(Config, Name, 2) ->
+ #{cert := NewRoot, key := Key} = public_key:pkix_test_root_cert(Name, []),
+ {[OwnCert, CA0, CA1, CA2, OldRoot], OldRoot} = chain_and_root(Config),
+ CA3 = new_intermediat(CA2, Key),
+ {[OwnCert, CA0, CA1, CA2, CA3], NewRoot}.
+
+extraneous_der_conf(Chain, NewRoot, OrgRoots,Config0) ->
+ CaCerts = proplists:get_value(cacerts, Config0),
+ Config1 = [{cert, Chain} | proplists:delete(cert, Config0)],
+ [{cacerts, [NewRoot | CaCerts -- OrgRoots]} | proplists:delete(cacerts, Config1)].
+
+extraneous_pem_conf(Chain, NewRoot, OldRoot, Config0, PrivDir) ->
+ Int = erlang:unique_integer(),
+ FileName = filename:join(PrivDir, "prop_test" ++ integer_to_list(Int)),
+ CaCerts = proplists:get_value(cacerts, Config0),
+ NewCas = [NewRoot | CaCerts -- [OldRoot]],
+ Entries = [{'Certificate', DerCert, not_encrypted} || DerCert <- Chain],
+ PemBin = public_key:pem_encode(Entries),
+ file:write_file(FileName, PemBin),
+ Config1 = [{cacerts, NewCas} | proplists:delete(cacerts, Config0)],
+ [{certfile, FileName} | proplists:delete(cert, Config1)].
+
+protocol('dtlsv1.2') ->
+ {protocol, dtls};
+protocol('dtlsv1') ->
+ {protocol, dtls};
+protocol(_) ->
+ {protocol,tls}.
+
+create_extraneous_chains([Client, _CCA0, _CCA1, CCA2, _CCA3], [Client, OCCA0, OCCA1, OCCA2, OCROOT],
+ [Server, _SCA0, _SCA1, SCA2, _SROOT], [Server, OSCA0, OSCA1, OSCA2, OSROOT]) ->
+ {[Client, OCCA0, OCCA1, CCA2, OCCA2, OCROOT], [Server, OSCA0, OSCA1, SCA2, OSCA2, OSROOT]}.
+create_extraneous_and_unorded([Client, _CCA0, _CCA1, CCA2, _CCA3], [Client, OCCA0, OCCA1, OCCA2, OCROOT],
+ [Server, _SCA0, _SCA1, SCA2, _SROOT], [Server, OSCA0, OSCA1, OSCA2, OSROOT]) ->
+ {[Client, OCCA0, CCA2, OCCA2, OCROOT, OCCA1], [Server, OSCA0, SCA2, OSCA2, OSROOT, OSCA1]}.
+
+root_key(ecdsa) ->
+ []; %% Just generate one
+root_key(rsa) ->
+ %% As rsa keygen is not guaranteed to be fast
+ [{key, ssl_test_lib:hardcode_rsa_key(6)}].
+
+peer_key(ecdsa) ->
+ []; %% Just generate one
+peer_key(rsa) ->
+ %% As rsa keygen is not guaranteed to be fast
+ [{key, ssl_test_lib:hardcode_rsa_key(6)}].
+
+intermediates(ecdsa, N) ->
+ lists:duplicate(N, []);
+intermediates(rsa, N) when N =< 4 ->
+ Default = lists:duplicate(N, []),
+ %% As rsa keygen is not guaranteed to be fast
+ hardcode_rsa_keys(Default, N, []).
+
+hardcode_rsa_keys([], 0, Acc) ->
+ Acc;
+hardcode_rsa_keys([Head | Tail], N, Acc) ->
+ hardcode_rsa_keys(Tail, N-1, [[{key, ssl_test_lib:hardcode_rsa_key(N)} | Head] | Acc]).
+
+new_intermediat(CA0, Key) ->
+ OTPCert = public_key:pkix_decode_cert(CA0, otp),
+ TBSCert = OTPCert#'OTPCertificate'.tbsCertificate,
+ Num = TBSCert#'OTPTBSCertificate'.serialNumber,
+ public_key:pkix_sign(TBSCert#'OTPTBSCertificate'{serialNumber = Num+1}, Key).
+
+
diff --git a/lib/ssl/test/property_test/ssl_eqc_cipher_format.erl b/lib/ssl/test/property_test/ssl_eqc_cipher_format.erl
index b661ec8806..cf6ed755f7 100644
--- a/lib/ssl/test/property_test/ssl_eqc_cipher_format.erl
+++ b/lib/ssl/test/property_test/ssl_eqc_cipher_format.erl
@@ -73,6 +73,21 @@ prop_tls_cipher_suite_rfc_name() ->
prop_tls_cipher_suite_openssl_name() ->
?FORALL({CipherSuite, _TLSVersion}, ?LET(Version, tls_version(), {cipher_suite(Version), Version}),
case ssl:str_to_suite(ssl:suite_to_openssl_str(CipherSuite)) of
+ CipherSuite ->
+ case ssl:suite_to_openssl_str(CipherSuite) of
+ "TLS_" ++ _ ->
+ true;
+ OpensslName ->
+ lists:member(OpensslName, openssl_legacy_names())
+ end;
+ _ ->
+ false
+ end
+ ).
+
+prop_tls_anon_cipher_suite_rfc_name() ->
+ ?FORALL({CipherSuite, _TLSVersion}, ?LET(Version, pre_tls_1_3_version(), {anon_cipher_suite(Version), Version}),
+ case ssl:str_to_suite(ssl:suite_to_str(CipherSuite)) of
CipherSuite ->
true;
_ ->
@@ -80,6 +95,15 @@ prop_tls_cipher_suite_openssl_name() ->
end
).
+prop_tls_anon_cipher_suite_openssl_name() ->
+ ?FORALL({CipherSuite, _TLSVersion}, ?LET(Version, pre_tls_1_3_version(), {anon_cipher_suite(Version), Version}),
+ case ssl:str_to_suite(ssl:suite_to_openssl_str(CipherSuite)) of
+ CipherSuite ->
+ lists:member(ssl:suite_to_openssl_str(CipherSuite), openssl_legacy_names());
+ _ ->
+ false
+ end
+ ).
%%--------------------------------------------------------------------
%% Generators -----------------------------------------------
@@ -87,9 +111,164 @@ prop_tls_cipher_suite_openssl_name() ->
tls_version() ->
oneof([?'TLS_v1.3', ?'TLS_v1.2', ?'TLS_v1.1', ?'TLS_v1']).
+pre_tls_1_3_version() ->
+ oneof([?'TLS_v1.2', ?'TLS_v1.1', ?'TLS_v1']).
+
cipher_suite(Version) ->
oneof(cipher_suites(Version)).
cipher_suites(Version) ->
- ssl:cipher_suites(all, Version).
+ ssl:cipher_suites(default, Version).
+
+anon_cipher_suite(Version) ->
+ oneof(ssl:cipher_suites(anonymous, Version)).
+
+openssl_legacy_names() ->
+ %% Only include names that we support
+ [
+ %% Legacy with RSA keyexchange
+ "AES128-SHA",
+ "AES256-SHA",
+ "AES128-SHA256",
+ "AES256-SHA256",
+ "AES256-GCM-SHA256",
+ "AES256-GCM-SHA384",
+ "DES-CBC-SHA",
+ "DES-CBC3-SHA",
+ "RC4-MD5",
+ "RC4-SHA",
+
+ %% DH based
+ "DH-RSA-AES128-SHA",
+ "DH-RSA-AES256-SHA",
+ "DH-RSA-AES128-SHA256",
+ "DH-RSA-AES256-SHA256",
+ "DH-DSS-AES128-SHA",
+ "DH-DSS-AES256-SHA",
+ "DH-DSS-AES128-SHA256",
+ "DH-DSS-AES256-SHA256",
+ "EDH-RSA-DES-CBC-SHA",
+ "EDH-RSA-DES-CBC3-SHA",
+ "DHE-RSA-AES128-SHA",
+ "DHE-RSA-AES256-SHA",
+ "DHE-RSA-AES128-SHA256",
+ "DHE-RSA-AES256-SHA256",
+ "DHE-RSA-AES256-SHA384",
+ "DHE-RSA-AES128-GCM-SHA256",
+ "DHE-RSA-AES256-GCM-SHA384",
+ "DHE-RSA-AES128-CCM-SHA256",
+ "DHE-RSA-AES256-CCM-SHA384",
+ "DHE-RSA-AES128-CCM8-SHA256",
+ "DHE-RSA-AES256-CCM8-SHA384",
+ "DHE-RSA-CHACHA20-POLY1305",
+ "EDH-DSS-DES-CBC-SHA",
+ "EDH-DSS-DES-CBC3-SHA",
+ "DHE-DSS-AES128-SHA",
+ "DHE-DSS-AES256-SHA",
+ "DHE-DSS-AES128-SHA256",
+ "DHE-DSS-AES256-SHA256",
+ "DHE-DSS-AES256-SHA384",
+ "DHE-DSS-AES128-GCM-SHA256",
+ "DHE-DSS-AES256-GCM-SHA384",
+ "DHE-DSS-RC4-SHA",
+ "ADH-AES128-SHA256",
+ "ADH-AES256-SHA256",
+ "ADH-AES128-CBC-SHA256",
+ "ADH-AES128-GCM-SHA256",
+ "ADH-AES256-GCM-SHA384",
+ "ADH-RC4-MD5",
+ "ADH-DES-CBC-SHA",
+ "ADH-DES-CBC3-SHA",
+ "ADH-AES256-SHA",
+ "ADH-AES256-SHA256",
+
+ %% ECDH based
+ "ECDH-ECDSA-AES128-SHA",
+ "ECDH-ECDSA-AES256-SHA",
+ "ECDH-ECDSA-AES128-SHA256",
+ "ECDH-ECDSA-AES256-SHA384",
+ "ECDH-ECDSA-AES128-GCM-SHA256",
+ "ECDH-ECDSA-AES256-GCM-SHA384",
+ "ECDHE-ECDSA-AES128-CCM",
+ "ECDHE-ECDSA-AES128-CCM8",
+ "ECDHE-ECDSA-AES256-CCM",
+ "ECDHE-ECDSA-AES256-CCM8",
+ "ECDH-ECDSA-CHACHA20-POLY1305",
+ "ECDHE-ECDSA-AES128-SHA",
+ "ECDHE-ECDSA-AES256-SHA",
+ "ECDHE-ECDSA-AES128-SHA256",
+ "ECDHE-ECDSA-AES256-SHA384",
+ "ECDHE-ECDSA-AES128-GCM-SHA256",
+ "ECDHE-ECDSA-AES256-GCM-SHA384",
+ "ECDHE-ECDSA-CHACHA20-POLY1305",
+ "ECDH-RSA-AES128-SHA",
+ "ECDH-RSA-AES256-SHA",
+ "ECDH-RSA-AES128-SHA256",
+ "ECDH-RSA-AES256-SHA384",
+ "ECDH-RSA-AES128-GCM-SHA256",
+ "ECDH-RSA-AES256-GCM-SHA384",
+ "ECDHE-RSA-AES128-SHA",
+ "ECDHE-RSA-AES256-SHA",
+ "ECDHE-RSA-AES128-SHA256",
+ "ECDHE-RSA-AES256-SHA384",
+ "ECDHE-RSA-AES128-GCM-SHA256",
+ "ECDHE-RSA-AES128-GCM-SHA384",
+ "ECDHE-RSA-AES256-GCM-SHA256",
+ "ECDHE-RSA-AES256-GCM-SHA384",
+ "ECDHE-RSA-CHACHA20-POLY1305",
+ "ECDHE-PSK-RC4-SHA",
+ "ECDHE-PSK-3DES-EDE-CBC-SHA",
+ "ECDHE-PSK-AES128-CBC-SHA",
+ "ECDHE-PSK-AES128-CBC-SHA256",
+ "ECDHE-PSK-AES256-CBC-SHA384",
+ "ECDHE-PSK-AES128-GCM-SHA256",
+ "ECDHE-PSK-AES256-GCM-SHA384",
+ "ECDHE-PSK-AES128-CCM-SHA256",
+ "ECDHE-PSK-AES128-CCM8-SHA256",
+ "ECDHE-PSK-CHACHA20-POLY1305",
+ "AECDH-DES-CBC3-SHA",
+ "AECDH-AES128-SHA",
+ "AECDH-AES256-SHA",
+ %% PSK based
+ "DHE-PSK-NULL-SHA",
+ "DHE-PSK-RC4-SHA",
+ "DHE-PSK-3DES-EDE-CBC-SHA",
+ "DHE-PSK-AES128-CBC-SHA",
+ "DHE-PSK-AES256-CBC-SHA",
+ "DHE-PSK-AES128-CBC-SHA256",
+ "DHE-PSK-AES256-CBC-SHA384",
+ "DHE-PSK-AES128-GCM-SHA256",
+ "DHE-PSK-AES256-GCM-SHA384",
+ "DHE-PSK-AES128-CCM",
+ "DHE-PSK-AES128-CCM8",
+ "DHE-PSK-AES256-CCM",
+ "DHE-PSK-AES256-CCM8",
+ "DHE-PSK-AES128-CCM-SHA256",
+ "DHE-PSK-AES128-CCM8-SHA256",
+ "DHE-PSK-CHACHA20-POLY1305",
+ "PSK-NULL-SHA",
+ "PSK-RC4-SHA",
+ "PSK-3DES-EDE-CBC-SHA",
+ "PSK-AES128-CBC-SHA",
+ "PSK-AES256-CBC-SHA",
+ "PSK-AES128-CBC-SHA256",
+ "PSK-AES256-CBC-SHA256",
+ "PSK-AES128-CCM",
+ "PSK-AES128-CCM8",
+ "PSK-AES256-CCM",
+ "PSK-AES256-CCM8",
+ "PSK-AES128-GCM-SHA256",
+ "PSK-AES256-CBC-SHA384",
+ "PSK-AES256-GCM-SHA384",
+ "PSK-CHACHA20-POLY1305",
+ "RSA-PSK-NULL-SHA",
+ "RSA-PSK-CHACHA20-POLY1305",
+
+ %% SRP based
+ "SRP-3DES-EDE-CBC-SHA",
+ "SRP-RSA-3DES-EDE-CBC-SHA",
+ "SRP-DSS-3DES-EDE-CBC-SHA",
+ "SRP-AES-128-CBC-SHA",
+ "SRP-AES-256-CBC-SHA"
+ ].
diff --git a/lib/ssl/test/ssl_ECC.erl b/lib/ssl/test/ssl_ECC.erl
index 56b67361e1..73dafcd1fc 100644
--- a/lib/ssl/test/ssl_ECC.erl
+++ b/lib/ssl/test/ssl_ECC.erl
@@ -23,6 +23,8 @@
-module(ssl_ECC).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
diff --git a/lib/ssl/test/ssl_ECC_SUITE.erl b/lib/ssl/test/ssl_ECC_SUITE.erl
index 496cb38fd9..c628f3b6d9 100644
--- a/lib/ssl/test/ssl_ECC_SUITE.erl
+++ b/lib/ssl/test/ssl_ECC_SUITE.erl
@@ -22,6 +22,8 @@
-module(ssl_ECC_SUITE).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
@@ -123,7 +125,8 @@ end_per_suite(_Config) ->
%%--------------------------------------------------------------------
init_per_group(GroupName, Config) ->
case ssl_test_lib:is_protocol_version(GroupName) of
- true ->
+ true ->
+ ct:log("Ciphers: ~p~n ", [ssl:cipher_suites(default, GroupName)]),
ssl_test_lib:init_per_group(GroupName,
[{client_type, erlang},
{server_type, erlang},
@@ -139,7 +142,6 @@ end_per_group(GroupName, Config) ->
init_per_testcase(TestCase, Config) ->
ssl_test_lib:ct_log_supported_protocol_versions(Config),
- ct:log("Ciphers: ~p~n ", [ ssl:cipher_suites()]),
end_per_testcase(TestCase, Config),
ssl:start(),
ct:timetrap({seconds, 15}),
diff --git a/lib/ssl/test/ssl_alert_SUITE.erl b/lib/ssl/test/ssl_alert_SUITE.erl
index ffdc9ea916..3b32d3ece1 100644
--- a/lib/ssl/test/ssl_alert_SUITE.erl
+++ b/lib/ssl/test/ssl_alert_SUITE.erl
@@ -20,6 +20,8 @@
-module(ssl_alert_SUITE).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
diff --git a/lib/ssl/test/ssl_alpn_SUITE.erl b/lib/ssl/test/ssl_alpn_SUITE.erl
index f0be740716..e3c10caa43 100644
--- a/lib/ssl/test/ssl_alpn_SUITE.erl
+++ b/lib/ssl/test/ssl_alpn_SUITE.erl
@@ -21,6 +21,8 @@
%%
-module(ssl_alpn_SUITE).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
%% Callback functions
@@ -324,7 +326,7 @@ ssl_receive(Socket, Data) ->
ssl_receive(Socket, Data, Buffer) ->
ct:log("Connection info: ~p~n",
- [ssl:connection_information(Socket)]),
+ [ssl:connection_information(Socket)]),
receive
{ssl, Socket, MoreData} ->
ct:log("Received ~p~n",[MoreData]),
diff --git a/lib/ssl/test/ssl_api_SUITE.erl b/lib/ssl/test/ssl_api_SUITE.erl
index 877458e776..6efd8424a9 100644
--- a/lib/ssl/test/ssl_api_SUITE.erl
+++ b/lib/ssl/test/ssl_api_SUITE.erl
@@ -21,6 +21,8 @@
%%
-module(ssl_api_SUITE).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
-include_lib("ssl/src/ssl_api.hrl").
@@ -52,6 +54,8 @@
connection_information/1,
secret_connection_info/0,
secret_connection_info/1,
+ keylog_connection_info/0,
+ keylog_connection_info/1,
versions/0,
versions/1,
active_n/0,
@@ -66,6 +70,8 @@
hibernate_right_away/1,
listen_socket/0,
listen_socket/1,
+ peername/0,
+ peername/1,
recv_active/0,
recv_active/1,
recv_active_once/0,
@@ -168,6 +174,7 @@
-export([connection_information_result/1,
connection_info_result/1,
secret_connection_info_result/1,
+ keylog_connection_info_result/2,
check_srp_in_connection_information/3,
check_connection_info/2,
prf_verify_value/4,
@@ -238,18 +245,21 @@ pre_1_3() ->
default_reject_anonymous,
connection_information_with_srp
].
+
gen_api_tests() ->
[
peercert,
peercert_with_client_cert,
connection_information,
secret_connection_info,
+ keylog_connection_info,
versions,
active_n,
dh_params,
hibernate,
hibernate_right_away,
listen_socket,
+ peername,
recv_active,
recv_active_once,
recv_active_n,
@@ -379,6 +389,15 @@ init_per_testcase(connection_information_with_srp, Config) ->
false ->
{skip, "Missing SRP crypto support"}
end;
+init_per_testcase(conf_signature_algs, Config) ->
+ case ssl_test_lib:appropriate_sha(crypto:supports()) of
+ sha256 ->
+ ssl_test_lib:ct_log_supported_protocol_versions(Config),
+ ct:timetrap({seconds, 10}),
+ Config;
+ sha ->
+ {skip, "Tests needs certs with sha256"}
+ end;
init_per_testcase(_TestCase, Config) ->
ssl_test_lib:ct_log_supported_protocol_versions(Config),
ct:timetrap({seconds, 10}),
@@ -568,6 +587,57 @@ secret_connection_info(Config) when is_list(Config) ->
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
%%--------------------------------------------------------------------
+keylog_connection_info() ->
+ [{doc,"Test the API function ssl:connection_information/2"}].
+keylog_connection_info(Config) when is_list(Config) ->
+ keylog_connection_info(Config, true),
+ keylog_connection_info(Config, false).
+keylog_connection_info(Config, KeepSecrets) ->
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Server =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, keylog_connection_info_result, [KeepSecrets]}},
+ {options, [{verify, verify_peer}, {keep_secrets, KeepSecrets} | ServerOpts]}]),
+
+ Port = ssl_test_lib:inet_port(Server),
+ Client =
+ ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, keylog_connection_info_result, [KeepSecrets]}},
+ {options, [{verify, verify_peer}, {keep_secrets, KeepSecrets} |ClientOpts]}]),
+
+ ct:log("Testcase ~p, KeepSecrets ~p Client ~p Server ~p ~n",
+ [self(), KeepSecrets, Client, Server]),
+
+ ServerKeylog = receive
+ {Server, {ok, Keylog}} ->
+ Keylog;
+ {Server, ServerError} ->
+ ct:fail({server, ServerError})
+ after 5000 ->
+ ct:fail({server, timeout})
+ end,
+
+ receive
+ {Client, {ok, ServerKeylog}} ->
+ ok;
+ {Client, {ok, ClientKeylog}} ->
+ ct:fail({mismatch, {ServerKeylog, ClientKeylog}});
+ {Client, ClientError} ->
+ ct:fail({client, ClientError})
+ after 5000 ->
+ ct:fail({client, timeout})
+ end,
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
+%%--------------------------------------------------------------------
prf() ->
[{doc,"Test that ssl:prf/5 uses the negotiated PRF."}].
prf(Config) when is_list(Config) ->
@@ -627,14 +697,16 @@ conf_signature_algs(Config) when is_list(Config) ->
ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
{from, self()},
{mfa, {ssl_test_lib, send_recv_result, []}},
- {options, [{active, false}, {signature_algs, [{sha256, rsa}]} | ServerOpts]}]),
+ {options, [{active, false}, {signature_algs, [{sha256, rsa}]},
+ {versions, ['tlsv1.2']} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
Client =
ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
{from, self()},
{mfa, {ssl_test_lib, send_recv_result, []}},
- {options, [{active, false}, {signature_algs, [{sha256, rsa}]} | ClientOpts]}]),
+ {options, [{active, false}, {signature_algs, [{sha256, rsa}]},
+ {versions, ['tlsv1.2']} | ClientOpts]}]),
ct:log("Testcase ~p, Client ~p Server ~p ~n",
[self(), Client, Server]),
@@ -715,7 +787,11 @@ handshake_continue_tls13_client(Config) when is_list(Config) ->
ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
-
+ SCiphers = ssl:filter_cipher_suites(ssl:cipher_suites(all, 'tlsv1.3'),
+ [{key_exchange, fun(srp_rsa) -> false;
+ (srp_anon) -> false;
+ (srp_dss) -> false;
+ (_) -> true end}]),
Server =
ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
{from, self()},
@@ -723,6 +799,7 @@ handshake_continue_tls13_client(Config) when is_list(Config) ->
{options, ssl_test_lib:ssl_options([{reuseaddr, true},
{log_level, debug},
{verify, verify_peer},
+ {ciphers, SCiphers},
{handshake, hello} | ServerOpts
],
Config)},
@@ -765,6 +842,7 @@ handshake_continue_tls13_client(Config) when is_list(Config) ->
'tlsv1.1',
'tlsv1'
]},
+ {ciphers, ssl:cipher_suites(all, 'tlsv1.3')},
{verify, verify_peer} | ClientOpts
],
Config)},
@@ -1090,6 +1168,46 @@ listen_socket(Config) ->
ok = ssl:close(ListenSocket).
%%--------------------------------------------------------------------
+peername() ->
+ [{doc,"Test API function peername/1"}].
+
+peername(Config) when is_list(Config) ->
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Server = ssl_test_lib:start_server(
+ [
+ {node, ServerNode}, {port, 0},
+ {from, self()},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+ {Client, CSocket} = ssl_test_lib:start_client(
+ [return_socket,
+ {node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {options, ClientOpts}]),
+
+ ct:log("Testcase ~p, Client ~p Server ~p ~n",
+ [self(), Client, Server]),
+
+ Server ! get_socket,
+ SSocket =
+ receive
+ {Server, {socket, Socket}} ->
+ Socket
+ end,
+
+ {ok, ServerPeer} = ssl:peername(SSocket),
+ ct:log("Server's peer: ~p~n", [ServerPeer]),
+ {ok, ClientPeer} = ssl:peername(CSocket),
+ ct:log("Client's peer: ~p~n", [ClientPeer]),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
+%%--------------------------------------------------------------------
recv_active() ->
[{doc,"Test recv on active socket"}].
@@ -2280,7 +2398,6 @@ cookie(Config) when is_list(Config) ->
cookie_extension(Config, true),
cookie_extension(Config, false).
-
%%% Checker functions
connection_information_result(Socket) ->
{ok, Info = [_ | _]} = ssl:connection_information(Socket),
@@ -2292,11 +2409,28 @@ connection_information_result(Socket) ->
false ->
ct:fail(no_ssl_options_returned)
end.
+
secret_connection_info_result(Socket) ->
{ok, [{protocol, Protocol}]} = ssl:connection_information(Socket, [protocol]),
{ok, ConnInfo} = ssl:connection_information(Socket, [client_random, server_random, master_secret]),
check_connection_info(Protocol, ConnInfo).
+keylog_connection_info_result(Socket, KeepSecrets) ->
+ {ok, [{protocol, Protocol}]} = ssl:connection_information(Socket, [protocol]),
+ {ok, ConnInfo} = ssl:connection_information(Socket, [keylog]),
+ check_keylog_info(Protocol, ConnInfo, KeepSecrets).
+
+check_keylog_info('tlsv1.3', [{keylog, ["CLIENT_HANDSHAKE_TRAFFIC_SECRET"++_,_|_]=Keylog}], true) ->
+ {ok, Keylog};
+check_keylog_info('tlsv1.3', []=Keylog, false) ->
+ {ok, Keylog};
+check_keylog_info('tlsv1.2', [{keylog, ["CLIENT_RANDOM"++_]=Keylog}], _) ->
+ {ok, Keylog};
+check_keylog_info(NotSup, [], _) when NotSup == 'tlsv1.1'; NotSup == tlsv1; NotSup == 'dtlsv1.2'; NotSup == dtlsv1 ->
+ {ok, []};
+check_keylog_info(_, Unexpected, _) ->
+ {unexpected, Unexpected}.
+
check_srp_in_connection_information(_Socket, _Username, client) ->
ok;
check_srp_in_connection_information(Socket, Username, server) ->
@@ -2437,8 +2571,8 @@ prf_ciphers_and_expected(TlsVer, PRFs, Results) ->
case TlsVer of
TlsVer when TlsVer == sslv3 orelse TlsVer == tlsv1
orelse TlsVer == 'tlsv1.1' orelse TlsVer == 'dtlsv1' ->
- Ciphers = ssl:cipher_suites(),
- {_, Expected} = lists:keyfind(md5sha, 1, Results),
+ Ciphers = ssl:cipher_suites(default, TlsVer),
+ Expected = [Expect#{prf := md5sha} || Expect <- Results],
[[{tls_ver, TlsVer}, {ciphers, Ciphers}, {expected, Expected}, {prf, md5sha}]];
TlsVer when TlsVer == 'tlsv1.2' orelse TlsVer == 'dtlsv1.2'->
lists:foldl(
@@ -2449,22 +2583,23 @@ prf_ciphers_and_expected(TlsVer, PRFs, Results) ->
ct:log("No ciphers for PRF algorithm ~p. Skipping.", [PRF]),
Acc;
Ciphers ->
- {_, Expected} = lists:keyfind(PRF, 1, Results),
+ Expected = [Expect#{prf := PRF} || Expect <- Results],
[[{tls_ver, TlsVer}, {ciphers, Ciphers}, {expected, Expected},
{prf, PRF}] | Acc]
end
end, [], PRFs)
end.
-prf_get_ciphers(_, PRF) ->
- lists:filter(
- fun(C) when tuple_size(C) == 4 andalso
- element(4, C) == PRF ->
- true;
- (_) ->
- false
- end,
- ssl:cipher_suites()).
+prf_get_ciphers(TlsVer, PRF) ->
+ PrfFilter = fun(Value) ->
+ case Value of
+ PRF ->
+ true;
+ _ ->
+ false
+ end
+ end,
+ ssl:filter_cipher_suites(ssl:cipher_suites(default, TlsVer), [{prf, PrfFilter}]).
prf_run_test(_, TlsVer, [], _, Prf) ->
ct:fail({error, cipher_list_empty, TlsVer, Prf});
diff --git a/lib/ssl/test/ssl_app_env_SUITE.erl b/lib/ssl/test/ssl_app_env_SUITE.erl
index ee9c3ad745..98e96023b2 100644
--- a/lib/ssl/test/ssl_app_env_SUITE.erl
+++ b/lib/ssl/test/ssl_app_env_SUITE.erl
@@ -21,6 +21,8 @@
%%
-module(ssl_app_env_SUITE).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
-include_lib("ssl/src/ssl_api.hrl").
diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl
index 0039b0244f..a5ece4fad8 100644
--- a/lib/ssl/test/ssl_basic_SUITE.erl
+++ b/lib/ssl/test/ssl_basic_SUITE.erl
@@ -22,7 +22,10 @@
-module(ssl_basic_SUITE).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
+-include_lib("public_key/include/public_key.hrl").
-include_lib("ssl/src/ssl_api.hrl").
%% Callback functions
@@ -62,12 +65,21 @@
unordered_protocol_versions_server/0,
unordered_protocol_versions_server/1,
unordered_protocol_versions_client/0,
- unordered_protocol_versions_client/1
+ unordered_protocol_versions_client/1,
+ fake_root/0,
+ fake_root/1,
+ fake_root_legacy/0,
+ fake_root_legacy/1,
+ fake_root_no_intermediate/0,
+ fake_root_no_intermediate/1,
+ fake_root_no_intermediate_legacy/0,
+ fake_root_no_intermediate_legacy/1,
+ fake_intermediate_cert/0,
+ fake_intermediate_cert/1
]).
%% Apply export
--export([send_recv_result/1,
- tcp_send_recv_result/1,
+-export([tcp_send_recv_result/1,
result_ok/1,
protocol_info_result/1,
version_info_result/1,
@@ -110,7 +122,12 @@ basic_tests() ->
eccs,
cipher_suites,
old_cipher_suites,
- cipher_suites_mix
+ cipher_suites_mix,
+ fake_root,
+ fake_root_no_intermediate,
+ fake_root_legacy,
+ fake_root_no_intermediate_legacy,
+ fake_intermediate_cert
].
options_tests() ->
@@ -291,7 +308,7 @@ cipher_suites(Config) when is_list(Config) ->
cipher => 'aes_128_cbc',
mac => sha,
prf => default_prf},
- Version = ssl_test_lib:protocol_version(Config),
+ Version = tls_record:highest_protocol_version([]),
All = [_|_] = ssl:cipher_suites(all, Version),
Default = [_|_] = ssl:cipher_suites(default, Version),
Anonymous = [_|_] = ssl:cipher_suites(anonymous, Version),
@@ -496,14 +513,252 @@ tls_versions_option(Config) when is_list(Config) ->
end,
ssl_test_lib:check_client_alert(ErrClient, protocol_version).
+fake_root() ->
+ [{doc,"Test that we can not use a fake root signed by other key but with correct name and serial number."}].
+fake_root(Config) when is_list(Config) ->
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Ext = x509_test:extensions([{key_usage, [keyCertSign, cRLSign, digitalSignature, keyAgreement]}]),
+ ROOT = #{cert := Cert,
+ key := _Key} = public_key:pkix_test_root_cert("SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(6)},
+ {extensions, Ext}]),
+
+ FakeKey = ssl_test_lib:hardcode_rsa_key(1),
+ OTPCert = public_key:pkix_decode_cert(Cert, otp),
+ TBS = OTPCert#'OTPCertificate'.tbsCertificate,
+ FakeCert = public_key:pkix_sign(TBS, FakeKey),
+
+
+ AuthExt = #'AuthorityKeyIdentifier'{authorityCertIssuer = [{directoryName, TBS#'OTPTBSCertificate'.issuer}],
+ authorityCertSerialNumber = TBS#'OTPTBSCertificate'.serialNumber},
+ [AuthKeyExt] = x509_test:extensions([{?'id-ce-authorityKeyIdentifier',
+ AuthExt,
+ false}]),
+
+ #{server_config := ServerConf,
+ client_config := ClientConf} = public_key:pkix_test_data(#{server_chain =>
+ #{root => ROOT,
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)},
+ {extensions, [AuthKeyExt]}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(4)}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
+ ),
+
+ #{server_config := FakeServerConf} = public_key:pkix_test_data(#{server_chain =>
+ #{root => #{cert => FakeCert, key => FakeKey},
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {extensions, [AuthKeyExt]}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
+ ),
+
+
+ test_fake_root(Hostname, ServerNode, ClientNode, ServerConf, ClientConf, FakeCert, FakeServerConf, bad_certificate, bad_certificate).
+
+fake_root_no_intermediate() ->
+ [{doc,"Test that we can not use a fake root signed by other key but with correct name and serial number."}].
+
+fake_root_no_intermediate(Config) when is_list(Config) ->
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Ext = x509_test:extensions([{key_usage, [keyCertSign, cRLSign, digitalSignature, keyAgreement]}]),
+ ROOT = #{cert := Cert,
+ key := _Key} = public_key:pkix_test_root_cert("SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(6)},
+ {extensions, Ext}]),
+
+ FakeKey = ssl_test_lib:hardcode_rsa_key(1),
+ OTPCert = public_key:pkix_decode_cert(Cert, otp),
+ TBS = OTPCert#'OTPCertificate'.tbsCertificate,
+ FakeCert = public_key:pkix_sign(TBS, FakeKey),
+
+ AuthExt = #'AuthorityKeyIdentifier'{authorityCertIssuer = [{directoryName, TBS#'OTPTBSCertificate'.issuer}],
+ authorityCertSerialNumber = TBS#'OTPTBSCertificate'.serialNumber},
+ [AuthKeyExt] = x509_test:extensions([{?'id-ce-authorityKeyIdentifier',
+ AuthExt,
+ false}]),
+
+
+ #{server_config := ServerConf,
+ client_config := ClientConf} = public_key:pkix_test_data(#{server_chain =>
+ #{root => ROOT,
+ intermediates => [],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(4)},
+ {extensions, [AuthKeyExt]}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
+ ),
+
+ #{server_config := FakeServerConf} = public_key:pkix_test_data(#{server_chain =>
+ #{root => #{cert => FakeCert, key => FakeKey},
+ intermediates => [],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {extensions, [AuthKeyExt]}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
+ ),
+ test_fake_root(Hostname, ServerNode, ClientNode, ServerConf, ClientConf, FakeCert, FakeServerConf, bad_certificate, bad_certificate).
+
+fake_root_legacy() ->
+ [{doc,"Test that we can not use a fake root signed by other key but with correct name and serial number."}].
+fake_root_legacy(Config) when is_list(Config) ->
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Ext = x509_test:extensions([{key_usage, [keyCertSign, cRLSign, digitalSignature, keyAgreement]}]),
+ ROOT = #{cert := Cert,
+ key := _Key} = public_key:pkix_test_root_cert("SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(6)},
+ {extensions, Ext}]),
+
+ FakeKey = ssl_test_lib:hardcode_rsa_key(1),
+ OTPCert = public_key:pkix_decode_cert(Cert, otp),
+ TBS = OTPCert#'OTPCertificate'.tbsCertificate,
+ FakeCert = public_key:pkix_sign(TBS, FakeKey),
+
+ #{server_config := ServerConf,
+ client_config := ClientConf} = public_key:pkix_test_data(#{server_chain =>
+ #{root => ROOT,
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(4)}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
+ ),
+
+ #{server_config := FakeServerConf} = public_key:pkix_test_data(#{server_chain =>
+ #{root => #{cert => FakeCert, key => FakeKey},
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
+ ),
+
+
+ test_fake_root(Hostname, ServerNode, ClientNode, ServerConf, ClientConf, FakeCert, FakeServerConf, unknown_ca, unknown_ca).
+
+fake_root_no_intermediate_legacy() ->
+ [{doc,"Test that we can not use a fake root signed by other key but with correct name and serial number."}].
+fake_root_no_intermediate_legacy(Config) when is_list(Config) ->
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Ext = x509_test:extensions([{key_usage, [keyCertSign, cRLSign, digitalSignature, keyAgreement]}]),
+ ROOT = #{cert := Cert,
+ key := _Key} = public_key:pkix_test_root_cert("SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(6)},
+ {extensions, Ext}]),
+
+ FakeKey = ssl_test_lib:hardcode_rsa_key(1),
+ OTPCert = public_key:pkix_decode_cert(Cert, otp),
+ TBS = OTPCert#'OTPCertificate'.tbsCertificate,
+ FakeCert = public_key:pkix_sign(TBS, FakeKey),
+
+ #{server_config := ServerConf,
+ client_config := ClientConf} = public_key:pkix_test_data(#{server_chain =>
+ #{root => ROOT,
+ intermediates => [],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(4)}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
+ ),
+
+ #{server_config := FakeServerConf} = public_key:pkix_test_data(#{server_chain =>
+ #{root => #{cert => FakeCert, key => FakeKey},
+ intermediates => [],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
+ ),
+ test_fake_root(Hostname, ServerNode, ClientNode, ServerConf, ClientConf, FakeCert, FakeServerConf, unknown_ca, unknown_ca).
+
+fake_intermediate_cert() ->
+ [{doc,"Test that we can not use a fake intermediat cert claiming to be signed by a trusted ROOT but is not."}].
+
+fake_intermediate_cert(Config) when is_list(Config) ->
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Ext = x509_test:extensions([{key_usage, [keyCertSign, cRLSign, digitalSignature, keyAgreement]}]),
+ ROOT = #{cert := Cert,
+ key := _Key} = public_key:pkix_test_root_cert("SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(6)},
+ {extensions, Ext}]),
+
+ OtherSROOT = #{cert := OtherSCert,
+ key := OtherSKey} = public_key:pkix_test_root_cert("OTHER SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {extensions, Ext}]),
+ OtherCROOT = #{cert := OtherCCert,
+ key := _OtherCKey} = public_key:pkix_test_root_cert("OTHER Client ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(1)},
+ {extensions, Ext}]),
+
+ #{client_config := ClientConf} = public_key:pkix_test_data(#{server_chain =>
+ #{root => ROOT,
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(4)}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
+ ),
+
+ #{server_config := OtherServerConf} = public_key:pkix_test_data(#{server_chain =>
+ #{root => OtherSROOT,
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(1)}]},
+ client_chain =>
+ #{root => OtherCROOT,
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
+ ),
+ OTPCert = public_key:pkix_decode_cert(Cert, otp),
+ TBS = OTPCert#'OTPCertificate'.tbsCertificate,
+ TBSExt = TBS#'OTPTBSCertificate'.extensions,
+ AuthExt = #'AuthorityKeyIdentifier'{authorityCertIssuer = [{directoryName, TBS#'OTPTBSCertificate'.issuer}],
+ authorityCertSerialNumber = TBS#'OTPTBSCertificate'.serialNumber},
+ [AuthKeyExt] = x509_test:extensions([{?'id-ce-authorityKeyIdentifier',
+ AuthExt,
+ false}]),
+
+
+ CAs = proplists:get_value(cacerts, OtherServerConf),
+
+ [ICA] = CAs -- [OtherSCert, OtherCCert],
+
+ OTPICACert = public_key:pkix_decode_cert(ICA, otp),
+ ICATBS = OTPICACert#'OTPCertificate'.tbsCertificate,
+
+ FakeICA = public_key:pkix_sign(ICATBS#'OTPTBSCertificate'{extensions = [AuthKeyExt | TBSExt]}, OtherSKey),
+
+ ServerCert = proplists:get_value(cert, OtherServerConf),
+ FakeServer = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {options, [{cert, [ServerCert, FakeICA]} |
+ proplists:delete(cert, OtherServerConf)]
+ }]),
+ Port1 = ssl_test_lib:inet_port(FakeServer),
+
+ Client1 = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port1},
+ {host, Hostname},
+ {from, self()},
+ {options, [{verify, verify_peer} | ClientConf]}]),
+
+ ssl_test_lib:check_client_alert(Client1, bad_certificate).
%%--------------------------------------------------------------------
%% callback functions ------------------------------------------------
%%--------------------------------------------------------------------
-send_recv_result(Socket) ->
- ssl:send(Socket, "Hello world"),
- {ok,"Hello world"} = ssl:recv(Socket, 11),
- ok.
tcp_send_recv_result(Socket) ->
gen_tcp:send(Socket, "Hello world"),
{ok,"Hello world"} = gen_tcp:recv(Socket, 11),
@@ -574,3 +829,60 @@ remove_supported_versions(Available, Supported) ->
Versions0
end.
+
+test_fake_root(Hostname, ServerNode, ClientNode, ServerConf, ClientConf, FakeCert, FakeServerConf, ResultRootIncluded, ResultRootExcluded) ->
+ RealServer = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, send_recv_result_active, []}},
+ {options, ServerConf}]),
+ Port0 = ssl_test_lib:inet_port(RealServer),
+ Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port0},
+ {host, Hostname},
+ {mfa, {ssl_test_lib, send_recv_result_active, []}},
+ {from, self()},
+ {options, [{verify, verify_peer} | ClientConf]}]),
+
+ ssl_test_lib:check_result(RealServer, ok, Client0, ok),
+
+ ssl_test_lib:close(RealServer),
+ ssl_test_lib:close(Client0),
+
+ %% Fake server sends ROOT cert
+ FakeServer = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {options, FakeServerConf}]),
+ Port1 = ssl_test_lib:inet_port(FakeServer),
+
+ Client1 = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port1},
+ {host, Hostname},
+ {from, self()},
+ {options, [{verify, verify_peer} | ClientConf]}]),
+
+ ssl_test_lib:check_client_alert(Client1, ResultRootIncluded),
+
+
+ %%Fake server does not send ROOT cert
+ CAS0 = proplists:get_value(cacerts, FakeServerConf),
+ CAS1 = CAS0 -- [FakeCert],
+
+ FakeServer1 = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {options, [{cacerts, CAS1} | proplists:delete(cacerts, FakeServerConf)]}]),
+
+ Port2 = ssl_test_lib:inet_port(FakeServer1),
+
+ Client2 = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port2},
+ {host, Hostname},
+ {from, self()},
+ {options, [{verify, verify_peer} | ClientConf]}]),
+
+ ssl_test_lib:check_client_alert(Client2, ResultRootExcluded),
+
+ ssl_test_lib:close(FakeServer1).
+
+
+
+
+
diff --git a/lib/ssl/test/ssl_bench_SUITE.erl b/lib/ssl/test/ssl_bench_SUITE.erl
index fe30a83a7e..f78ac9c9cc 100644
--- a/lib/ssl/test/ssl_bench_SUITE.erl
+++ b/lib/ssl/test/ssl_bench_SUITE.erl
@@ -19,6 +19,8 @@
%%
-module(ssl_bench_SUITE).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct_event.hrl").
%% Callback functions
diff --git a/lib/ssl/test/ssl_bench_test_lib.erl b/lib/ssl/test/ssl_bench_test_lib.erl
index cbb9cfe47a..74ab142993 100644
--- a/lib/ssl/test/ssl_bench_test_lib.erl
+++ b/lib/ssl/test/ssl_bench_test_lib.erl
@@ -19,6 +19,8 @@
%%
-module(ssl_bench_test_lib).
+-behaviour(ct_suite).
+
%% API
-export([setup/1]).
diff --git a/lib/ssl/test/ssl_cert_SUITE.erl b/lib/ssl/test/ssl_cert_SUITE.erl
index bec6057d91..efa01fda76 100644
--- a/lib/ssl/test/ssl_cert_SUITE.erl
+++ b/lib/ssl/test/ssl_cert_SUITE.erl
@@ -21,6 +21,8 @@
%%
-module(ssl_cert_SUITE).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
@@ -54,6 +56,8 @@
client_auth_partial_chain_fun_fail/1,
client_auth_sni/0,
client_auth_sni/1,
+ client_auth_seelfsigned_peer/0,
+ client_auth_seelfsigned_peer/1,
missing_root_cert_no_auth/0,
missing_root_cert_no_auth/1,
missing_root_cert_auth/0,
@@ -199,6 +203,7 @@ all_version_tests() ->
client_auth_do_not_allow_partial_chain,
client_auth_partial_chain_fun_fail,
client_auth_sni,
+ client_auth_seelfsigned_peer,
missing_root_cert_no_auth,
missing_root_cert_auth,
missing_root_cert_auth_user_verify_fun_accept,
@@ -382,6 +387,11 @@ client_auth_sni() ->
ssl_cert_tests:client_auth_sni().
client_auth_sni(Config) when is_list(Config) ->
ssl_cert_tests:client_auth_sni(Config).
+%%--------------------------------------------------------------------
+client_auth_seelfsigned_peer() ->
+ ssl_cert_tests:client_auth_seelfsigned_peer().
+client_auth_seelfsigned_peer(Config) when is_list(Config) ->
+ ssl_cert_tests:client_auth_seelfsigned_peer(Config).
%%--------------------------------------------------------------------
missing_root_cert_no_auth() ->
diff --git a/lib/ssl/test/ssl_cert_tests.erl b/lib/ssl/test/ssl_cert_tests.erl
index 5422ff7fe4..d9d535106a 100644
--- a/lib/ssl/test/ssl_cert_tests.erl
+++ b/lib/ssl/test/ssl_cert_tests.erl
@@ -21,6 +21,8 @@
%%
-module(ssl_cert_tests).
+-behaviour(ct_suite).
+
-include_lib("public_key/include/public_key.hrl").
%% Test cases
@@ -42,6 +44,8 @@
client_auth_partial_chain_fun_fail/1,
client_auth_sni/0,
client_auth_sni/1,
+ client_auth_seelfsigned_peer/0,
+ client_auth_seelfsigned_peer/1,
missing_root_cert_no_auth/0,
missing_root_cert_no_auth/1,
invalid_signature_client/0,
@@ -235,6 +239,18 @@ client_auth_sni(Config) when is_list(Config) ->
ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, handshake_failure).
%%--------------------------------------------------------------------
+client_auth_seelfsigned_peer() ->
+ [{doc, "Check that selfsigned peer raises alert"}].
+client_auth_seelfsigned_peer(Config) when is_list(Config) ->
+ Ext = x509_test:extensions([{key_usage, [keyCertSign, cRLSign, digitalSignature, keyAgreement]}]),
+ #{cert := Cert,
+ key := Key} = public_key:pkix_test_root_cert("OTP test server ROOT", [{key, ssl_test_lib:hardcode_rsa_key(6)},
+ {extensions, Ext}]),
+ DerKey = public_key:der_encode('RSAPrivateKey', Key),
+ ssl_test_lib:basic_alert(ssl_test_lib:ssl_options([{verify, verify_peer}, {cacerts , [Cert]}], Config),
+ ssl_test_lib:ssl_options([{cert, Cert},
+ {key, {'RSAPrivateKey', DerKey}}], Config), Config, bad_certificate).
+%%--------------------------------------------------------------------
missing_root_cert_no_auth() ->
[{doc,"Test that the client succeds if the ROOT CA is unknown in verify_none mode"}].
diff --git a/lib/ssl/test/ssl_cipher_SUITE.erl b/lib/ssl/test/ssl_cipher_SUITE.erl
index eee8d8078b..31e60269f8 100644
--- a/lib/ssl/test/ssl_cipher_SUITE.erl
+++ b/lib/ssl/test/ssl_cipher_SUITE.erl
@@ -20,6 +20,8 @@
-module(ssl_cipher_SUITE).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
-include("tls_record.hrl").
-include("ssl_cipher.hrl").
diff --git a/lib/ssl/test/ssl_cipher_suite_SUITE.erl b/lib/ssl/test/ssl_cipher_suite_SUITE.erl
index 1aab56f924..22dbc3663c 100644
--- a/lib/ssl/test/ssl_cipher_suite_SUITE.erl
+++ b/lib/ssl/test/ssl_cipher_suite_SUITE.erl
@@ -22,6 +22,8 @@
-module(ssl_cipher_suite_SUITE).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
%% Callback functions
-export([all/0,
@@ -122,10 +124,14 @@
aes_128_gcm_sha256/1,
chacha20_poly1305_sha256/1,
aes_128_ccm_sha256/1,
- aes_128_ccm_8_sha256/1
+ aes_128_ccm_8_sha256/1,
+ ecdhe_ecdsa_with_aes_128_ccm/1,
+ ecdhe_ecdsa_with_aes_256_ccm/1,
+ ecdhe_ecdsa_with_aes_128_ccm_8/1,
+ ecdhe_ecdsa_with_aes_256_ccm_8/1
]).
--define(TIMEOUT, {seconds, 5}).
+-define(TIMEOUT, {seconds, 10}).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
@@ -169,7 +175,11 @@ groups() ->
ecdhe_ecdsa_aes_128_gcm,
ecdhe_ecdsa_aes_256_cbc,
ecdhe_ecdsa_aes_256_gcm,
- ecdhe_ecdsa_chacha20_poly1305
+ ecdhe_ecdsa_chacha20_poly1305,
+ ecdhe_ecdsa_with_aes_128_ccm,
+ ecdhe_ecdsa_with_aes_256_ccm,
+ ecdhe_ecdsa_with_aes_128_ccm_8,
+ ecdhe_ecdsa_with_aes_256_ccm_8
]},
{rsa, [], [rsa_3des_ede_cbc,
rsa_aes_128_cbc,
@@ -480,6 +490,26 @@ init_per_testcase(aes_128_ccm_8_sha256, Config) ->
_ ->
{skip, "Missing AES_128_CCM_8_SHA256 crypto support"}
end;
+init_per_testcase(TestCase, Config) when TestCase == ecdhe_ecdsa_with_aes_128_ccm;
+ TestCase == ecdhe_ecdsa_with_aes_128_ccm_8->
+ SupCiphers = proplists:get_value(ciphers, crypto:supports()),
+ case lists:member(aes_128_ccm, SupCiphers) of
+ true ->
+ ct:timetrap(?TIMEOUT),
+ Config;
+ _ ->
+ {skip, "Missing AES_128_CCM crypto support"}
+ end;
+init_per_testcase(TestCase, Config) when TestCase == ecdhe_ecdsa_with_aes_256_ccm;
+ TestCase == ecdhe_ecdsa_with_aes_256_ccm_8 ->
+ SupCiphers = proplists:get_value(ciphers, crypto:supports()),
+ case lists:member(aes_256_ccm, SupCiphers) of
+ true ->
+ ct:timetrap(?TIMEOUT),
+ Config;
+ _ ->
+ {skip, "Missing AES_256_CCM crypto support"}
+ end;
init_per_testcase(TestCase, Config) ->
Cipher = ssl_test_lib:test_cipher(TestCase, Config),
SupCiphers = proplists:get_value(ciphers, crypto:supports()),
@@ -742,6 +772,18 @@ ecdhe_ecdsa_aes_256_gcm(Config) when is_list(Config) ->
ecdhe_ecdsa_chacha20_poly1305(Config) when is_list(Config) ->
run_ciphers_test(ecdhe_ecdsa, 'chacha20_poly1305', Config).
+
+ecdhe_ecdsa_with_aes_128_ccm(Config) when is_list(Config) ->
+ run_ciphers_test(ecdhe_ecdsa, 'aes_128_ccm', Config).
+
+ecdhe_ecdsa_with_aes_256_ccm(Config) when is_list(Config) ->
+ run_ciphers_test(ecdhe_ecdsa, 'aes_256_ccm', Config).
+
+ecdhe_ecdsa_with_aes_128_ccm_8(Config) when is_list(Config) ->
+ run_ciphers_test(ecdhe_ecdsa, 'aes_128_ccm_8', Config).
+
+ecdhe_ecdsa_with_aes_256_ccm_8(Config) when is_list(Config) ->
+ run_ciphers_test(ecdhe_ecdsa, 'aes_256_ccm_8', Config).
%%--------------------------------------------------------------------
%% DHE_DSS --------------------------------------------------------
%%--------------------------------------------------------------------
diff --git a/lib/ssl/test/ssl_crl_SUITE.erl b/lib/ssl/test/ssl_crl_SUITE.erl
index ad75d29a78..f9fac82962 100644
--- a/lib/ssl/test/ssl_crl_SUITE.erl
+++ b/lib/ssl/test/ssl_crl_SUITE.erl
@@ -21,6 +21,8 @@
-module(ssl_crl_SUITE).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
diff --git a/lib/ssl/test/ssl_dist_SUITE.erl b/lib/ssl/test/ssl_dist_SUITE.erl
index 20463114f4..019e22eaa8 100644
--- a/lib/ssl/test/ssl_dist_SUITE.erl
+++ b/lib/ssl/test/ssl_dist_SUITE.erl
@@ -20,6 +20,8 @@
-module(ssl_dist_SUITE).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
-include("ssl_dist_test_lib.hrl").
diff --git a/lib/ssl/test/ssl_dist_bench_SUITE.erl b/lib/ssl/test/ssl_dist_bench_SUITE.erl
index 67944c74d2..2216c6b04b 100644
--- a/lib/ssl/test/ssl_dist_bench_SUITE.erl
+++ b/lib/ssl/test/ssl_dist_bench_SUITE.erl
@@ -19,6 +19,8 @@
%%
-module(ssl_dist_bench_SUITE).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct_event.hrl").
-include_lib("public_key/include/public_key.hrl").
diff --git a/lib/ssl/test/ssl_dist_test_lib.erl b/lib/ssl/test/ssl_dist_test_lib.erl
index fa1f4217fe..bf010e6ad4 100644
--- a/lib/ssl/test/ssl_dist_test_lib.erl
+++ b/lib/ssl/test/ssl_dist_test_lib.erl
@@ -20,6 +20,8 @@
-module(ssl_dist_test_lib).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
-include("ssl_dist_test_lib.hrl").
diff --git a/lib/ssl/test/ssl_engine_SUITE.erl b/lib/ssl/test/ssl_engine_SUITE.erl
index 4e88c6e46c..c0e28120be 100644
--- a/lib/ssl/test/ssl_engine_SUITE.erl
+++ b/lib/ssl/test/ssl_engine_SUITE.erl
@@ -21,6 +21,8 @@
%%
-module(ssl_engine_SUITE).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
diff --git a/lib/ssl/test/ssl_eqc_SUITE.erl b/lib/ssl/test/ssl_eqc_SUITE.erl
index 52edb39d9c..3c9a1d0ab0 100644
--- a/lib/ssl/test/ssl_eqc_SUITE.erl
+++ b/lib/ssl/test/ssl_eqc_SUITE.erl
@@ -20,6 +20,7 @@
-module(ssl_eqc_SUITE).
+-behaviour(ct_suite).
%% Common test
-export([all/0,
@@ -32,7 +33,13 @@
%% Test cases
-export([tls_handshake_encoding/1,
tls_cipher_suite_names/1,
- tls_cipher_openssl_suite_names/1
+ tls_cipher_openssl_suite_names/1,
+ tls_anon_cipher_suite_names/1,
+ tls_anon_cipher_openssl_suite_names/1,
+ tls_unorded_chains/1,
+ tls_extraneous_chain/1,
+ tls_extraneous_chains/1,
+ tls_extraneous_and_unorder_chains/1
]).
%%--------------------------------------------------------------------
@@ -43,7 +50,13 @@ all() ->
[
tls_handshake_encoding,
tls_cipher_suite_names,
- tls_cipher_openssl_suite_names
+ tls_cipher_openssl_suite_names,
+ tls_anon_cipher_suite_names,
+ tls_anon_cipher_openssl_suite_names,
+ tls_unorded_chains,
+ tls_extraneous_chain,
+ tls_extraneous_chains,
+ tls_extraneous_and_unorder_chains
].
%%--------------------------------------------------------------------
@@ -76,3 +89,37 @@ tls_cipher_openssl_suite_names(Config) when is_list(Config) ->
%% manual test: proper:quickcheck(ssl_eqc_handshake:prop_tls_cipher_suite_openssl_name()).
true = ct_property_test:quickcheck(ssl_eqc_cipher_format:prop_tls_cipher_suite_openssl_name(),
Config).
+tls_anon_cipher_suite_names(Config) when is_list(Config) ->
+ %% manual test: proper:quickcheck(ssl_eqc_cipher_format:prop_tls_cipher_suite_rfc_name()).
+ true = ct_property_test:quickcheck(ssl_eqc_cipher_format:prop_tls_anon_cipher_suite_rfc_name(),
+ Config).
+
+tls_anon_cipher_openssl_suite_names(Config) when is_list(Config) ->
+ %% manual test: proper:quickcheck(ssl_eqc_handshake:prop_tls_cipher_suite_openssl_name()).
+ true = ct_property_test:quickcheck(ssl_eqc_cipher_format:prop_tls_anon_cipher_suite_openssl_name(),
+ Config).
+
+tls_unorded_chains(Config) when is_list(Config) ->
+ %% manual test: proper:quickcheck(ssl_eqc_chain:prop_tls_ordered_path("/tmp")
+ ssl:start(),
+ PrivDir = proplists:get_value(priv_dir, Config),
+ true = ct_property_test:quickcheck(ssl_eqc_chain:prop_tls_unordered_path(PrivDir),
+ Config).
+
+tls_extraneous_chain(Config) when is_list(Config) ->
+ %% manual test: proper:quickcheck(ssl_eqc_chain:prop_tls_ordered_path("/tmp")
+ ssl:start(),
+ PrivDir = proplists:get_value(priv_dir, Config),
+ true = ct_property_test:quickcheck(ssl_eqc_chain:prop_tls_extraneous_path(PrivDir),
+ Config).
+
+tls_extraneous_chains(Config) when is_list(Config) ->
+ %% manual test: proper:quickcheck(ssl_eqc_chain:prop_tls_ordered_path()
+ ssl:start(),
+ true = ct_property_test:quickcheck(ssl_eqc_chain:prop_tls_extraneous_paths(),
+ Config).
+tls_extraneous_and_unorder_chains(Config) when is_list(Config) ->
+ %% manual test: proper:quickcheck(ssl_eqc_chain:prop_tls_ordered_path()
+ ssl:start(),
+ true = ct_property_test:quickcheck(ssl_eqc_chain:prop_tls_extraneous_and_unordered_path(),
+ Config).
diff --git a/lib/ssl/test/ssl_handshake_SUITE.erl b/lib/ssl/test/ssl_handshake_SUITE.erl
index f990b9328a..df8f867f24 100644
--- a/lib/ssl/test/ssl_handshake_SUITE.erl
+++ b/lib/ssl/test/ssl_handshake_SUITE.erl
@@ -22,6 +22,8 @@
-module(ssl_handshake_SUITE).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
-include("ssl_alert.hrl").
-include("ssl_handshake.hrl").
@@ -50,7 +52,6 @@
decode_empty_server_sni_correctly/1,
select_proper_tls_1_2_rsa_default_hashsign/1,
ignore_hassign_extension_pre_tls_1_2/1,
- unorded_chain/1,
signature_algorithms/1,
encode_decode_srp/1]).
@@ -66,7 +67,7 @@ all() -> [decode_hello_handshake,
decode_empty_server_sni_correctly,
select_proper_tls_1_2_rsa_default_hashsign,
ignore_hassign_extension_pre_tls_1_2,
- unorded_chain, signature_algorithms,
+ signature_algorithms,
encode_decode_srp].
%%--------------------------------------------------------------------
@@ -197,24 +198,6 @@ ignore_hassign_extension_pre_tls_1_2(Config) ->
{md5sha, rsa} = ssl_handshake:select_hashsign({HashSigns, undefined}, Cert, ecdhe_rsa, tls_v1:default_signature_algs({3,2}), {3,2}),
{md5sha, rsa} = ssl_handshake:select_hashsign({HashSigns, undefined}, Cert, ecdhe_rsa, tls_v1:default_signature_algs({3,0}), {3,0}).
-unorded_chain(Config) when is_list(Config) ->
- DefConf = ssl_test_lib:default_cert_chain_conf(),
- CertChainConf = ssl_test_lib:gen_conf(rsa, rsa, DefConf, DefConf),
- #{server_config := ServerConf,
- client_config := _ClientConf} = public_key:pkix_test_data(CertChainConf),
- PeerCert = proplists:get_value(cert, ServerConf),
- CaCerts = [_, C1, C2] = proplists:get_value(cacerts, ServerConf),
- {ok, ExtractedCerts} = ssl_pkix_db:extract_trusted_certs({der, CaCerts}),
- UnordedChain = case public_key:pkix_is_self_signed(C1) of
- true ->
- [C1, C2];
- false ->
- [C2, C1]
- end,
- OrderedChain = [PeerCert | lists:reverse(UnordedChain)],
- {ok, _, OrderedChain} =
- ssl_certificate:certificate_chain(PeerCert, ets:new(foo, []), ExtractedCerts, UnordedChain).
-
encode_decode_srp(_Config) ->
Exts = #{srp => #srp{username = <<"foo">>},
sni => #sni{hostname = "bar"},
diff --git a/lib/ssl/test/ssl_key_update_SUITE.erl b/lib/ssl/test/ssl_key_update_SUITE.erl
index 2816f1a39e..aba6a02ddc 100644
--- a/lib/ssl/test/ssl_key_update_SUITE.erl
+++ b/lib/ssl/test/ssl_key_update_SUITE.erl
@@ -19,6 +19,8 @@
%%
-module(ssl_key_update_SUITE).
+-behaviour(ct_suite).
+
%% Callback functions
-export([all/0,
groups/0,
diff --git a/lib/ssl/test/ssl_mfl_SUITE.erl b/lib/ssl/test/ssl_mfl_SUITE.erl
index e8fa0ddf52..0f9aeb1c67 100644
--- a/lib/ssl/test/ssl_mfl_SUITE.erl
+++ b/lib/ssl/test/ssl_mfl_SUITE.erl
@@ -18,6 +18,9 @@
%% %CopyrightEnd%
%%
-module(ssl_mfl_SUITE).
+
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
%% Common test
diff --git a/lib/ssl/test/ssl_npn_SUITE.erl b/lib/ssl/test/ssl_npn_SUITE.erl
index addf105e59..81c75ecff0 100644
--- a/lib/ssl/test/ssl_npn_SUITE.erl
+++ b/lib/ssl/test/ssl_npn_SUITE.erl
@@ -21,6 +21,8 @@
%%
-module(ssl_npn_SUITE).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
%% Callback functions
diff --git a/lib/ssl/test/ssl_npn_hello_SUITE.erl b/lib/ssl/test/ssl_npn_hello_SUITE.erl
index ee84383c61..dae07aae63 100644
--- a/lib/ssl/test/ssl_npn_hello_SUITE.erl
+++ b/lib/ssl/test/ssl_npn_hello_SUITE.erl
@@ -22,6 +22,8 @@
-module(ssl_npn_hello_SUITE).
+-behaviour(ct_suite).
+
-include_lib("ssl/src/tls_record.hrl").
-include_lib("ssl/src/tls_handshake.hrl").
-include_lib("ssl/src/ssl_cipher.hrl").
diff --git a/lib/ssl/test/ssl_packet_SUITE.erl b/lib/ssl/test/ssl_packet_SUITE.erl
index bd390d67bf..a65173f172 100644
--- a/lib/ssl/test/ssl_packet_SUITE.erl
+++ b/lib/ssl/test/ssl_packet_SUITE.erl
@@ -21,6 +21,8 @@
%%
-module(ssl_packet_SUITE).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
%% Callback functions
diff --git a/lib/ssl/test/ssl_payload_SUITE.erl b/lib/ssl/test/ssl_payload_SUITE.erl
index df8394ab14..e594c745b6 100644
--- a/lib/ssl/test/ssl_payload_SUITE.erl
+++ b/lib/ssl/test/ssl_payload_SUITE.erl
@@ -20,6 +20,8 @@
-module(ssl_payload_SUITE).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
%% Common test
-export([all/0,
diff --git a/lib/ssl/test/ssl_pem_cache_SUITE.erl b/lib/ssl/test/ssl_pem_cache_SUITE.erl
index 2469b47040..50d8089054 100644
--- a/lib/ssl/test/ssl_pem_cache_SUITE.erl
+++ b/lib/ssl/test/ssl_pem_cache_SUITE.erl
@@ -22,6 +22,8 @@
-module(ssl_pem_cache_SUITE).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/file.hrl").
diff --git a/lib/ssl/test/ssl_renegotiate_SUITE.erl b/lib/ssl/test/ssl_renegotiate_SUITE.erl
index 18ca56b6f9..4b46863415 100644
--- a/lib/ssl/test/ssl_renegotiate_SUITE.erl
+++ b/lib/ssl/test/ssl_renegotiate_SUITE.erl
@@ -22,6 +22,8 @@
-module(ssl_renegotiate_SUITE).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
diff --git a/lib/ssl/test/ssl_rfc_5869_SUITE.erl b/lib/ssl/test/ssl_rfc_5869_SUITE.erl
index d350dd099d..497a05107b 100644
--- a/lib/ssl/test/ssl_rfc_5869_SUITE.erl
+++ b/lib/ssl/test/ssl_rfc_5869_SUITE.erl
@@ -21,6 +21,8 @@
%%
-module(ssl_rfc_5869_SUITE).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
%% Common test
diff --git a/lib/ssl/test/ssl_session_SUITE.erl b/lib/ssl/test/ssl_session_SUITE.erl
index c9f457ce8a..b11e49ad89 100644
--- a/lib/ssl/test/ssl_session_SUITE.erl
+++ b/lib/ssl/test/ssl_session_SUITE.erl
@@ -21,6 +21,8 @@
%%
-module(ssl_session_SUITE).
+-behaviour(ct_suite).
+
-include("tls_handshake.hrl").
-include("ssl_record.hrl").
@@ -45,6 +47,8 @@
reuse_session_expired/1,
server_does_not_want_to_reuse_session/0,
server_does_not_want_to_reuse_session/1,
+ explicit_session_reuse/0,
+ explicit_session_reuse/1,
no_reuses_session_server_restart_new_cert/0,
no_reuses_session_server_restart_new_cert/1,
no_reuses_session_server_restart_new_cert_file/0,
@@ -81,6 +85,7 @@ session_tests() ->
[reuse_session,
reuse_session_expired,
server_does_not_want_to_reuse_session,
+ explicit_session_reuse,
no_reuses_session_server_restart_new_cert,
no_reuses_session_server_restart_new_cert_file].
@@ -138,8 +143,9 @@ reuse_session() ->
reuse_session(Config) when is_list(Config) ->
ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
-
- ssl_test_lib:reuse_session(ClientOpts, ServerOpts, Config).
+ Version = ssl_test_lib:protocol_version(Config),
+ ssl_test_lib:reuse_session([{versions,[Version]} | ClientOpts],
+ [{versions,[Version]} | ServerOpts], Config).
%%--------------------------------------------------------------------
reuse_session_expired() ->
[{doc,"Test sessions is not reused when it has expired"}].
@@ -268,6 +274,46 @@ server_does_not_want_to_reuse_session(Config) when is_list(Config) ->
ssl_test_lib:close(Server),
ssl_test_lib:close(Client1).
+%%--------------------------------------------------------------------
+explicit_session_reuse() ->
+ [{doc,"Test {reuse_session, {ID, Data}}} option for explicit reuse of sessions not"
+ " saved in the clients automated session reuse"}].
+explicit_session_reuse(Config) when is_list(Config) ->
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_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),
+ {Client0, Client0Sock} =
+ ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {from, self()}, {options, [{reuse_sessions, false} | ClientOpts]},
+ return_socket
+ ]),
+
+ {ok, [{session_id, ID}, {session_data, SessData}]} = ssl:connection_information(Client0Sock, [session_id, session_data]),
+
+ ssl_test_lib:close(Client0),
+
+ Server ! listen,
+
+ {_, Client1Sock} =
+ ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {from, self()}, {options, [{reuse_session, {ID, SessData}} | ClientOpts]},
+ return_socket]),
+
+ {ok, [{session_id, ID}]} = ssl:connection_information(Client1Sock, [session_id]).
+
+
+%%--------------------------------------------------------------------
no_reuses_session_server_restart_new_cert() ->
[{doc,"Check that a session is not reused if the server is restarted with a new cert."}].
no_reuses_session_server_restart_new_cert(Config) when is_list(Config) ->
@@ -435,18 +481,6 @@ faulty_client(Host, Port) ->
gen_tcp:close(Sock).
-server(LOpts, Port) ->
- {ok, LSock} = ssl:listen(Port, LOpts),
- Pid = spawn_link(?MODULE, accept_loop, [LSock]),
- ssl:controlling_process(LSock, Pid),
- Pid.
-
-accept_loop(Sock) ->
- {ok, CSock} = ssl:transport_accept(Sock),
- _ = ssl:handshake(CSock),
- accept_loop(Sock).
-
-
encode_client_hello(CH, Random) ->
HSBin = tls_handshake:encode_handshake(CH, {3,3}),
CS = connection_states(Random),
diff --git a/lib/ssl/test/ssl_session_cache_SUITE.erl b/lib/ssl/test/ssl_session_cache_SUITE.erl
index c53161f168..7c182ee063 100644
--- a/lib/ssl/test/ssl_session_cache_SUITE.erl
+++ b/lib/ssl/test/ssl_session_cache_SUITE.erl
@@ -22,6 +22,8 @@
-module(ssl_session_cache_SUITE).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
%% Callback functions
diff --git a/lib/ssl/test/ssl_session_ticket_SUITE.erl b/lib/ssl/test/ssl_session_ticket_SUITE.erl
index 218c4a1564..16791e2c36 100644
--- a/lib/ssl/test/ssl_session_ticket_SUITE.erl
+++ b/lib/ssl/test/ssl_session_ticket_SUITE.erl
@@ -20,6 +20,8 @@
-module(ssl_session_ticket_SUITE).
+-behaviour(ct_suite).
+
%% Callback functions
-export([all/0,
groups/0,
diff --git a/lib/ssl/test/ssl_sni_SUITE.erl b/lib/ssl/test/ssl_sni_SUITE.erl
index 1014dda5af..9e2f9fddb1 100644
--- a/lib/ssl/test/ssl_sni_SUITE.erl
+++ b/lib/ssl/test/ssl_sni_SUITE.erl
@@ -21,6 +21,8 @@
-module(ssl_sni_SUITE).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
-include_lib("kernel/include/inet.hrl").
@@ -131,7 +133,7 @@ end_per_suite(_) ->
init_per_testcase(customize_hostname_check, Config) ->
ssl_test_lib:ct_log_supported_protocol_versions(Config),
- ssl_test_lib:clean_start(),
+ ssl_test_lib:clean_start(keep_version),
ct:timetrap(?TIMEOUT),
Config;
init_per_testcase(_TestCase, Config) ->
diff --git a/lib/ssl/test/ssl_socket_SUITE.erl b/lib/ssl/test/ssl_socket_SUITE.erl
index a6bce2a414..e10ec5afaf 100644
--- a/lib/ssl/test/ssl_socket_SUITE.erl
+++ b/lib/ssl/test/ssl_socket_SUITE.erl
@@ -20,6 +20,8 @@
-module(ssl_socket_SUITE).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl
index 207a08ec7f..ab09e4e67a 100644
--- a/lib/ssl/test/ssl_test_lib.erl
+++ b/lib/ssl/test/ssl_test_lib.erl
@@ -21,10 +21,13 @@
%%
-module(ssl_test_lib).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
-export([clean_start/0,
+ clean_start/1,
clean_env/0,
init_per_group/2,
init_per_group_openssl/2,
@@ -128,7 +131,9 @@
session_info_result/1,
reuse_session/3,
test_ciphers/3,
- test_cipher/2
+ test_cipher/2,
+ openssl_ciphers/0,
+ openssl_support_rsa_kex/0
]).
-export([tls_version/1,
@@ -164,7 +169,8 @@
ecdh_dh_anonymous_suites/1,
ecdsa_suites/1,
der_to_pem/2,
- pem_to_der/1
+ pem_to_der/1,
+ appropriate_sha/1
]).
-export([maybe_force_ipv4/1,
@@ -263,24 +269,25 @@ get_client_opts(Config) ->
ssl_options(COpts, Config).
%% Default callback functions
-init_per_group(GroupName, Config) ->
+init_per_group(GroupName, Config0) ->
case is_protocol_version(GroupName) andalso sufficient_crypto_support(GroupName) of
true ->
- clean_protocol_version(Config),
- init_protocol_version(GroupName, Config);
+ Config = clean_protocol_version(Config0),
+ [{version, GroupName}|init_protocol_version(GroupName, Config)];
_ ->
case sufficient_crypto_support(GroupName) of
true ->
ssl:start(),
- Config;
+ Config0;
false ->
{skip, "Missing crypto support"}
end
end.
-init_per_group_openssl(GroupName, Config) ->
+init_per_group_openssl(GroupName, Config0) ->
case is_tls_version(GroupName) andalso sufficient_crypto_support(GroupName) of
true ->
+ Config = clean_protocol_version(Config0),
case openssl_tls_version_support(GroupName, Config)
of
true ->
@@ -292,7 +299,7 @@ init_per_group_openssl(GroupName, Config) ->
case sufficient_crypto_support(GroupName) of
true ->
ssl:start(),
- Config;
+ Config0;
false ->
{skip, "Missing crypto support"}
end
@@ -314,6 +321,24 @@ openssl_ocsp_support() ->
false
end.
+openssl_ciphers() ->
+ Str = portable_cmd("openssl", ["ciphers"]),
+ Ciphers = string:split(string:strip(Str, right, $\n), ":", all),
+ case portable_cmd("openssl", ["version"]) of
+ "LibreSSL 3." ++ _ ->
+ Ciphers -- ["DES-CBC3-SHA","AES128-SHA", "AES256-SHA", "RC4-SHA", "RC4-MD5"];
+ _ ->
+ Ciphers
+ end.
+
+openssl_support_rsa_kex() ->
+ case portable_cmd("openssl", ["version"]) of
+ "OpenSSL 1.1.1" ++ _Rest ->
+ false;
+ _ ->
+ true
+ end.
+
%%====================================================================
%% Internal functions
%%====================================================================
@@ -640,7 +665,7 @@ init_openssl_server(openssl, _, Options) ->
Pid = proplists:get_value(from, Options),
Exe = "openssl",
- Ciphers = proplists:get_value(ciphers, Options, ssl:cipher_suites(default,Version)),
+ Ciphers = proplists:get_value(ciphers, Options, default_ciphers(Version)),
Groups0 = proplists:get_value(groups, Options),
CertArgs = openssl_cert_options(Options, server),
AlpnArgs = openssl_alpn_options(proplists:get_value(alpn, Options, undefined)),
@@ -1606,11 +1631,16 @@ make_rsa_1024_cert(Config) ->
appropriate_sha(CryptoSupport) ->
Hashes = proplists:get_value(hashs, CryptoSupport),
- case lists:member(sha256, Hashes) of
- true ->
- sha256;
- false ->
- sha1
+ case portable_cmd("openssl", ["version"]) of
+ "OpenSSL 0.9.8" ++ _ ->
+ sha;
+ _ ->
+ case lists:member(sha256, Hashes) of
+ true ->
+ sha256;
+ false ->
+ sha
+ end
end.
%% RFC 4492, Sect. 2.3. ECDH_RSA
@@ -2007,13 +2037,12 @@ start_server(openssl, ClientOpts, ServerOpts, Config) ->
start_server(erlang, _, ServerOpts, Config) ->
{_, ServerNode, _} = run_where(Config),
KeyEx = proplists:get_value(check_keyex, Config, false),
- Versions = protocol_versions(Config),
Server = start_server([{node, ServerNode}, {port, 0},
{from, self()},
{mfa, {ssl_test_lib,
check_key_exchange_send_active,
[KeyEx]}},
- {options, [{verify, verify_peer}, {versions, Versions} | ServerOpts]}]),
+ {options, [{verify, verify_peer} | ServerOpts]}]),
{Server, inet_port(Server)}.
sig_algs(undefined) ->
@@ -2064,7 +2093,6 @@ openssl_maxfag_option(Int) ->
openssl_debug_options() ->
["-msg", "-debug"].
-
start_server_with_raw_key(erlang, ServerOpts, Config) ->
{_, ServerNode, _} = run_where(Config),
Server = start_server([{node, ServerNode}, {port, 0},
@@ -2131,7 +2159,8 @@ openssl_cert_options(Opts, Role) ->
Other
end;
_ ->
- cert_option("-cert", Cert) ++ cert_option("-CAfile", CA) ++
+ cert_option("-cert", Cert) ++ cert_option("-CAfile", CA)
+ ++ cert_option("-cert_chain", CA) ++
cert_option("-key", Key) ++ openssl_verify(Opts) ++ ["2"]
end.
@@ -2145,6 +2174,13 @@ openssl_verify(Opts) ->
cert_option(_, undefined) ->
[];
+cert_option("-cert_chain", Value) ->
+ case portable_cmd("openssl", ["version"]) of
+ "OpenSSL 1.1.1" ++ _ ->
+ ["-cert_chain", Value];
+ _ ->
+ ""
+ end;
cert_option(Opt, Value) ->
[Opt, Value].
@@ -2399,7 +2435,7 @@ init_protocol_version(Version, Config) ->
[{protocol, tls} | NewConfig].
clean_protocol_version(Config) ->
- proplists:delete(protocol_opts, proplists:delete(protocol, Config)).
+ proplists:delete(version, proplists:delete(protocol_opts, proplists:delete(protocol, Config))).
sufficient_crypto_support(Version)
when Version == 'tlsv1.3' ->
@@ -2594,12 +2630,18 @@ active_recv(_Socket, N, Acc) when N < 0 ->
active_recv(Socket, N, Acc) ->
receive
{ssl, Socket, Bytes} ->
- active_recv(Socket, N-length(Bytes), Acc ++ Bytes);
+ active_recv(Socket, N-data_length(Bytes), Acc ++ Bytes);
{Socket, {data, Bytes0}} ->
Bytes = filter_openssl_debug_data(Bytes0),
- active_recv(Socket, N-length(Bytes), Acc ++ Bytes)
+ active_recv(Socket, N-data_length(Bytes), Acc ++ Bytes)
end.
+
+data_length(Bytes) when is_list(Bytes) ->
+ length(Bytes);
+data_length(Bytes) when is_binary(Bytes)->
+ size(Bytes).
+
filter_openssl_debug_data(Bytes) ->
re:replace(Bytes,
"(read.*\n|write to.*\n|[\\dabcdefABCDEF]{4,4} -.*\n|>>> .*\n|<<< .*\n| \\d\\d.*\n|KEYUPDATE\n|.*Read BLOCK\n)*",
@@ -3055,12 +3097,29 @@ clean_env() ->
application:unset_env(ssl, bypass_pem_cache),
application:unset_env(ssl, alert_timeout),
application:unset_env(ssl, internal_active_n).
+%%
+clean_env(keep_version) ->
+ application:unset_env(ssl, session_lifetime),
+ application:unset_env(ssl, session_cb),
+ application:unset_env(ssl, session_cb_init_args),
+ application:unset_env(ssl, session_cache_client_max),
+ application:unset_env(ssl, session_cache_server_max),
+ application:unset_env(ssl, ssl_pem_cache_clean),
+ application:unset_env(ssl, bypass_pem_cache),
+ application:unset_env(ssl, alert_timeout),
+ application:unset_env(ssl, internal_active_n).
clean_start() ->
ssl:stop(),
application:load(ssl),
clean_env(),
ssl:start().
+%%
+clean_start(keep_version) ->
+ ssl:stop(),
+ application:load(ssl),
+ clean_env(keep_version),
+ ssl:start().
is_psk_anon_suite({psk, _,_}) ->
true;
@@ -3549,9 +3608,21 @@ assert_mfl(Socket, MFL) ->
bigger_buffers() ->
case os:type() of
{unix,sunos} ->
- [{recbuf, ?BIG_BUF},{sndbuf, ?BIG_BUF}];
+ [{buffer, ?BIG_BUF}, {recbuf, ?BIG_BUF},{sndbuf, ?BIG_BUF}];
{unix,openbsd} ->
- [{recbuf, ?BIG_BUF},{sndbuf, ?BIG_BUF}];
+ [{buffer, ?BIG_BUF}, {recbuf, ?BIG_BUF},{sndbuf, ?BIG_BUF}];
_ ->
[]
end.
+
+default_ciphers(Version) ->
+ OpenSSLCiphers = openssl_ciphers(),
+ Ciphers =
+ case portable_cmd("openssl", ["version"]) of
+ "OpenSSL 0.9" ++ _ ->
+ ssl:cipher_suites(all,Version);
+ _ ->
+ ssl:cipher_suites(default, Version)
+ end,
+ [Cipher || Cipher <- Ciphers, lists:member(ssl:suite_to_openssl_str(Cipher), OpenSSLCiphers)].
+
diff --git a/lib/ssl/test/ssl_upgrade_SUITE.erl b/lib/ssl/test/ssl_upgrade_SUITE.erl
index b258b9b057..cc5db69788 100644
--- a/lib/ssl/test/ssl_upgrade_SUITE.erl
+++ b/lib/ssl/test/ssl_upgrade_SUITE.erl
@@ -19,6 +19,8 @@
%%
-module(ssl_upgrade_SUITE).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
%% Common test
diff --git a/lib/ssl/test/tls_1_3_version_SUITE.erl b/lib/ssl/test/tls_1_3_version_SUITE.erl
index 3fd1683029..03fcb9afe5 100644
--- a/lib/ssl/test/tls_1_3_version_SUITE.erl
+++ b/lib/ssl/test/tls_1_3_version_SUITE.erl
@@ -59,25 +59,27 @@
%%--------------------------------------------------------------------
all() ->
[
- {group, 'tlsv1.3'}
+ cert_groups()
].
groups() ->
[
- {'tlsv1.3', [], cert_groups()},
- {rsa, [], tests()},
- {ecdsa, [], tests()}
+ {rsa, [], tls_1_3_1_2_tests() ++ legacy_tests()},
+ {ecdsa, [], tls_1_3_1_2_tests()}
].
cert_groups() ->
[{group, rsa},
{group, ecdsa}].
-tests() ->
+tls_1_3_1_2_tests() ->
[tls13_client_tls12_server,
tls13_client_with_ext_tls12_server,
tls12_client_tls13_server,
- tls_client_tls10_server,
+ tls_client_tls12_server,
+ tls12_client_tls_server].
+legacy_tests() ->
+ [tls_client_tls10_server,
tls_client_tls11_server,
tls_client_tls12_server,
tls10_client_tls_server,
@@ -88,9 +90,14 @@ init_per_suite(Config) ->
catch crypto:stop(),
try crypto:start() of
ok ->
- ssl_test_lib:clean_start(),
- [{client_type, erlang}, {server_type, erlang} |
- Config]
+ case ssl_test_lib:sufficient_crypto_support('tlsv1.3') of
+ true ->
+ ssl_test_lib:clean_start(),
+ [{client_type, erlang}, {server_type, erlang} |
+ Config];
+ false ->
+ {skip, "Insufficient crypto support for TLS-1.3"}
+ end
catch _:_ ->
{skip, "Crypto did not start"}
end.
@@ -99,23 +106,14 @@ end_per_suite(_Config) ->
ssl:stop(),
application:stop(crypto).
-init_per_group(GroupName, Config) ->
- case ssl_test_lib:is_protocol_version(GroupName) of
- true ->
- ssl_test_lib:init_per_group(GroupName,
- [{client_type, erlang},
- {server_type, erlang} | Config]);
- false ->
- do_init_per_group(GroupName, Config)
- end.
-
-do_init_per_group(rsa, Config0) ->
+init_per_group(rsa, Config0) ->
Config = ssl_test_lib:make_rsa_cert(Config0),
COpts = proplists:get_value(client_rsa_opts, Config),
SOpts = proplists:get_value(server_rsa_opts, Config),
- [{client_cert_opts, COpts}, {server_cert_opts, SOpts} |
+ [{client_type, erlang},
+ {server_type, erlang},{client_cert_opts, COpts}, {server_cert_opts, SOpts} |
lists:delete(server_cert_opts, lists:delete(client_cert_opts, Config))];
-do_init_per_group(ecdsa, Config0) ->
+init_per_group(ecdsa, Config0) ->
PKAlg = crypto:supports(public_keys),
case lists:member(ecdsa, PKAlg) andalso
(lists:member(ecdh, PKAlg) orelse lists:member(dh, PKAlg)) of
@@ -123,7 +121,8 @@ do_init_per_group(ecdsa, Config0) ->
Config = ssl_test_lib:make_ecdsa_cert(Config0),
COpts = proplists:get_value(client_ecdsa_opts, Config),
SOpts = proplists:get_value(server_ecdsa_opts, Config),
- [{client_cert_opts, COpts}, {server_cert_opts, SOpts} |
+ [{client_type, erlang},
+ {server_type, erlang},{client_cert_opts, COpts}, {server_cert_opts, SOpts} |
lists:delete(server_cert_opts, lists:delete(client_cert_opts, Config))];
false ->
{skip, "Missing EC crypto support"}
@@ -175,21 +174,38 @@ tls12_client_tls13_server(Config) when is_list(Config) ->
tls_client_tls10_server() ->
[{doc,"Test that a TLS 1.0-1.3 client can connect to a TLS 1.0 server."}].
tls_client_tls10_server(Config) when is_list(Config) ->
+ CCiphers = ssl:filter_cipher_suites(ssl:cipher_suites(all, 'tlsv1.3'),
+ [{key_exchange, fun(srp_rsa) -> false;
+ (srp_anon) -> false;
+ (srp_dss) -> false;
+ (_) -> true end}]),
ClientOpts = [{versions,
- ['tlsv1', 'tlsv1.1', 'tlsv1.2', 'tlsv1.3']} |
+ ['tlsv1', 'tlsv1.1', 'tlsv1.2', 'tlsv1.3']},
+ {ciphers, CCiphers}
+ |
ssl_test_lib:ssl_options(client_cert_opts, Config)],
ServerOpts = [{versions,
- ['tlsv1']} | ssl_test_lib:ssl_options(server_cert_opts, Config)],
+ ['tlsv1']},
+ {ciphers, ssl:cipher_suites(all, 'tlsv1')}
+ | ssl_test_lib:ssl_options(server_cert_opts, Config)],
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
tls_client_tls11_server() ->
[{doc,"Test that a TLS 1.0-1.3 client can connect to a TLS 1.1 server."}].
tls_client_tls11_server(Config) when is_list(Config) ->
+ CCiphers = ssl:filter_cipher_suites(ssl:cipher_suites(all, 'tlsv1.3'),
+ [{key_exchange, fun(srp_rsa) -> false;
+ (srp_anon) -> false;
+ (srp_dss) -> false;
+ (_) -> true end}]),
ClientOpts = [{versions,
- ['tlsv1', 'tlsv1.1', 'tlsv1.2', 'tlsv1.3']} |
+ ['tlsv1', 'tlsv1.1', 'tlsv1.2', 'tlsv1.3']},
+ {ciphers, CCiphers} |
ssl_test_lib:ssl_options(client_cert_opts, Config)],
ServerOpts = [{versions,
- ['tlsv1.1']} | ssl_test_lib:ssl_options(server_cert_opts, Config)],
+ ['tlsv1.1']},
+ {ciphers, ssl:cipher_suites(all, 'tlsv1.1')}
+ | ssl_test_lib:ssl_options(server_cert_opts, Config)],
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
tls_client_tls12_server() ->
@@ -205,20 +221,36 @@ tls_client_tls12_server(Config) when is_list(Config) ->
tls10_client_tls_server() ->
[{doc,"Test that a TLS 1.0 client can connect to a TLS 1.0-1.3 server."}].
tls10_client_tls_server(Config) when is_list(Config) ->
+ SCiphers = ssl:filter_cipher_suites(ssl:cipher_suites(all, 'tlsv1.3'),
+ [{key_exchange, fun(srp_rsa) -> false;
+ (srp_anon) -> false;
+ (srp_dss) -> false;
+ (_) -> true end}]),
ClientOpts = [{versions,
- ['tlsv1']} | ssl_test_lib:ssl_options(client_cert_opts, Config)],
+ ['tlsv1']}, {ciphers, ssl:cipher_suites(all, 'tlsv1')} | ssl_test_lib:ssl_options(client_cert_opts, Config)],
ServerOpts = [{versions,
- ['tlsv1','tlsv1.1', 'tlsv1.2', 'tlsv1.3']} |
+ ['tlsv1','tlsv1.1', 'tlsv1.2', 'tlsv1.3']},
+ {ciphers, SCiphers}
+ |
ssl_test_lib:ssl_options(server_cert_opts, Config)],
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
tls11_client_tls_server() ->
[{doc,"Test that a TLS 1.1 client can connect to a TLS 1.0-1.3 server."}].
tls11_client_tls_server(Config) when is_list(Config) ->
+ SCiphers = ssl:filter_cipher_suites(ssl:cipher_suites(all, 'tlsv1.3'),
+ [{key_exchange, fun(srp_rsa) -> false;
+ (srp_anon) -> false;
+ (srp_dss) -> false;
+ (_) -> true end}]),
+
ClientOpts = [{versions,
- ['tlsv1.1']} | ssl_test_lib:ssl_options(client_cert_opts, Config)],
+ ['tlsv1.1']}, {ciphers, ssl:cipher_suites(all, 'tlsv1.1')} |
+ ssl_test_lib:ssl_options(client_cert_opts, Config)],
ServerOpts = [{versions,
- ['tlsv1','tlsv1.1', 'tlsv1.2', 'tlsv1.3']} |
+ ['tlsv1','tlsv1.1', 'tlsv1.2', 'tlsv1.3']},
+ {ciphers, SCiphers}
+ |
ssl_test_lib:ssl_options(server_cert_opts, Config)],
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
diff --git a/lib/ssl/test/tls_api_SUITE.erl b/lib/ssl/test/tls_api_SUITE.erl
index 82b785faa4..26f086f11c 100644
--- a/lib/ssl/test/tls_api_SUITE.erl
+++ b/lib/ssl/test/tls_api_SUITE.erl
@@ -55,6 +55,8 @@
tls_client_closes_socket/1,
tls_closed_in_active_once/0,
tls_closed_in_active_once/1,
+ tls_reset_in_active_once/0,
+ tls_reset_in_active_once/1,
tls_tcp_msg/0,
tls_tcp_msg/1,
tls_tcp_msg_big/0,
@@ -123,6 +125,7 @@ api_tests() ->
tls_shutdown_error,
tls_client_closes_socket,
tls_closed_in_active_once,
+ tls_reset_in_active_once,
tls_tcp_msg,
tls_tcp_msg_big,
tls_dont_crash_on_handshake_garbage,
@@ -367,10 +370,10 @@ tls_client_closes_socket(Config) when is_list(Config) ->
ssl_test_lib:check_result(Server, {error,closed}).
%%--------------------------------------------------------------------
-tls_closed_in_active_once() ->
+tls_reset_in_active_once() ->
[{doc, "Test that ssl_closed is delivered in active once with non-empty buffer, check ERL-420."}].
-tls_closed_in_active_once(Config) when is_list(Config) ->
+tls_reset_in_active_once(Config) when is_list(Config) ->
ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{_ClientNode, _ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -396,6 +399,40 @@ tls_closed_in_active_once(Config) when is_list(Config) ->
ok -> ok;
_ -> ct:fail(Result)
end.
+
+%%--------------------------------------------------------------------
+tls_closed_in_active_once() ->
+ [{doc, "Test that active once can be used to deliver not only all data"
+ " but even the close message, see ERL-1409, in normal operation."
+ " This is also test, with slighly diffrent circumstances in"
+ " the old tls_closed_in_active_once test"
+ " renamed tls_reset_in_active_once"}].
+
+tls_closed_in_active_once(Config) when is_list(Config) ->
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ {_ClientNode, _ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ TcpOpts = [binary, {reuseaddr, true}],
+ Port = ssl_test_lib:inet_port(node()),
+ Server = fun() ->
+ {ok, Listen} = gen_tcp:listen(Port, TcpOpts),
+ {ok, TcpServerSocket} = gen_tcp:accept(Listen),
+ {ok, ServerSocket} = ssl:handshake(TcpServerSocket, ServerOpts),
+ lists:foreach(
+ fun(_) ->
+ ssl:send(ServerSocket, "some random message\r\n")
+ end, lists:seq(1, 20)),
+ ssl:close(ServerSocket)
+ end,
+ spawn_link(Server),
+ {ok, Socket} = ssl:connect(Hostname, Port, [{active, false} | ClientOpts]),
+ Result = tls_closed_in_active_once_loop(Socket),
+ ssl:close(Socket),
+ case Result of
+ ok -> ok;
+ _ -> ct:fail(Result)
+ end.
+
%%--------------------------------------------------------------------
tls_tcp_msg() ->
[{doc,"Test what happens when a tcp tries to connect, i,e. a bad (ssl) packet is sent first"}].
@@ -468,32 +505,35 @@ tls_dont_crash_on_handshake_garbage() ->
tls_dont_crash_on_handshake_garbage(Config) ->
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
-
+ Version = ssl_test_lib:protocol_version(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, send_recv_result_active, []}},
- {options, ServerOpts}]),
- unlink(Server), monitor(process, Server),
+ {from, self()},
+ {mfa, ssl_test_lib, no_result},
+ {options, [{versions, [Version]} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
-
+
{ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {active, false}]),
- % Send hello and garbage record
+ %% Send hello and garbage record
ok = gen_tcp:send(Socket,
[<<22, 3,3, 49:16, 1, 45:24, 3,3, % client_hello
16#deadbeef:256, % 32 'random' bytes = 256 bits
0, 6:16, 0,255, 0,61, 0,57, 1, 0 >>, % some hello values
-
<<22, 3,3, 5:16, 92,64,37,228,209>> % garbage
]),
- % Send unexpected change_cipher_spec
+ %% Send unexpected change_cipher_spec
ok = gen_tcp:send(Socket, <<20, 3,3, 12:16, 111,40,244,7,137,224,16,109,197,110,249,152>>),
-
+ gen_tcp:close(Socket),
% Ensure we receive an alert, not sudden disconnect
- {ok, <<21, _/binary>>} = drop_handshakes(Socket, 1000).
-
+ case Version of
+ 'tlsv1.3' ->
+ ssl_test_lib:check_server_alert(Server, illegal_parameter);
+ _ ->
+ ssl_test_lib:check_server_alert(Server, handshake_failure)
+ end.
+
%%--------------------------------------------------------------------
tls_tcp_error_propagation_in_active_mode() ->
[{doc,"Test that process recives {ssl_error, Socket, closed} when tcp error ocurres"}].
@@ -774,15 +814,9 @@ upgrade_result(Socket) ->
ok = ssl:send(Socket, "Hello world"),
%% Make sure binary is inherited from tcp socket and that we do
%% not get the list default!
- receive
- {ssl, _, <<"H">>} ->
- receive
- {ssl, _, <<"ello world">>} ->
- ok
- end;
- {ssl, _, <<"Hello world">>} ->
- ok
- end.
+ <<"Hello world">> = ssl_test_lib:active_recv(Socket, length("Hello world")),
+ ok.
+
tls_downgrade_result(Socket, Pid) ->
ok = ssl_test_lib:send_recv_result(Socket),
Pid ! {self(), ready},
@@ -794,16 +828,8 @@ tls_downgrade_result(Socket, Pid) ->
{ok, TCPSocket} ->
inet:setopts(TCPSocket, [{active, true}]),
gen_tcp:send(TCPSocket, "Downgraded"),
- receive
- {tcp, TCPSocket, <<"Downgraded">>} ->
- ct:sleep(?SLEEP),
- ok;
- {tcp_closed, TCPSocket} ->
- ct:fail("Did not receive TCP data"),
- ok;
- Other ->
- {error, Other}
- end;
+ <<"Downgraded">> = active_tcp_recv(TCPSocket, length("Downgraded")),
+ ok;
{error, timeout} ->
ct:comment("Timed out, downgrade aborted"),
ok;
@@ -843,19 +869,9 @@ tls_closed_in_active_once_loop(Socket) ->
tls_closed_in_active_once_loop(Socket);
{ssl_closed, Socket} ->
ok
- after 5000 ->
- no_ssl_closed_received
end;
{error, closed} ->
- ok
- end.
-
-drop_handshakes(Socket, Timeout) ->
- {ok, <<RecType:8, _RecMajor:8, _RecMinor:8, RecLen:16>> = Header} = gen_tcp:recv(Socket, 5, Timeout),
- {ok, <<Frag:RecLen/binary>>} = gen_tcp:recv(Socket, RecLen, Timeout),
- case RecType of
- 22 -> drop_handshakes(Socket, Timeout);
- _ -> {ok, <<Header/binary, Frag/binary>>}
+ {error, ssl_setopt_failed}
end.
receive_msg(_) ->
@@ -877,3 +893,14 @@ tls_socket_options_result(Socket, Options, DefaultValues, NewOptions, NewValues)
ct:log("All opts ~p~n", [All]),
ok.
+active_tcp_recv(Socket, N) ->
+ active_tcp_recv(Socket, N, []).
+
+active_tcp_recv(_Socket, 0, Acc) ->
+ Acc;
+active_tcp_recv(Socket, N, Acc) ->
+ receive
+ {tcp, Socket, Bytes} ->
+ active_tcp_recv(Socket, N-size(Bytes), Acc ++ Bytes)
+ end.
+
diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk
index b50afeb1be..2b9e7f5e6b 100644
--- a/lib/ssl/vsn.mk
+++ b/lib/ssl/vsn.mk
@@ -1 +1 @@
-SSL_VSN = 10.1
+SSL_VSN = 10.2.3
diff --git a/lib/stdlib/doc/src/Makefile b/lib/stdlib/doc/src/Makefile
index 1092ce3ffa..4b22e35e3b 100644
--- a/lib/stdlib/doc/src/Makefile
+++ b/lib/stdlib/doc/src/Makefile
@@ -101,7 +101,7 @@ XML_REF6_FILES = stdlib_app.xml
XML_PART_FILES = part.xml
XML_CHAPTER_FILES = introduction.xml io_protocol.xml unicode_usage.xml \
- notes.xml assert_hrl.xml
+ uri_string_usage.xml notes.xml assert_hrl.xml
BOOK_FILES = book.xml
diff --git a/lib/stdlib/doc/src/ets.xml b/lib/stdlib/doc/src/ets.xml
index 5f10818273..690e3558be 100644
--- a/lib/stdlib/doc/src/ets.xml
+++ b/lib/stdlib/doc/src/ets.xml
@@ -195,10 +195,20 @@
<seemfa marker="#tab2list/1"><c>tab2list/1</c></seemfa>.</p>
</item>
</list>
- <p>None of these ways of table traversal will guarantee a consistent table snapshot
- if the table is also updated during the traversal. Moreover, traversals not
- done in a <em>safe</em> way, on tables where keys are inserted or deleted
- during the traversal, may yield the following undesired effects:</p>
+ <p>
+ No table traversal will guarantee a consistent snapshot of the entire
+ table if the table is also updated by concurrent processes during the
+ traversal. The result of each concurrently updated object may be seen (or
+ not) depending on if it has happened when the traversal visits that part
+ of the table. The only way to guarantee a full consistent table snapshot
+ (if you really need that) is to disallow concurrent updates during the
+ entire traversal.
+ </p>
+ <p>
+ Moreover, traversals not done in a <em>safe</em> way, on tables where
+ keys are inserted or deleted during the traversal, may yield the
+ following undesired effects:
+ </p>
<list type="bulleted">
<item><p>Any key may be missed.</p></item>
<item><p>Any key may be found more than once.</p></item>
diff --git a/lib/stdlib/doc/src/notes.xml b/lib/stdlib/doc/src/notes.xml
index 96d1ffddc8..ad41d33d1d 100644
--- a/lib/stdlib/doc/src/notes.xml
+++ b/lib/stdlib/doc/src/notes.xml
@@ -31,6 +31,74 @@
</header>
<p>This document describes the changes made to the STDLIB application.</p>
+<section><title>STDLIB 3.14</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ This change fixes the handling of deep lists in the path
+ component when using uri_string:recompose/1.</p>
+ <p>
+ Own Id: OTP-16941</p>
+ </item>
+ <item>
+ <p>
+ Fix <seeerl
+ marker="shell_docs"><c>shell_docs</c></seeerl> to clear
+ shell decorations (bold/underline) when paginating
+ output.</p>
+ <p>
+ Fix various small renderings issues when integrating
+ <seeerl marker="shell_docs"><c>shell_docs</c></seeerl>
+ with edoc.</p>
+ <p>
+ Own Id: OTP-17047</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Improved the API and documentation of the uri_string
+ module.</p>
+ <p>
+ Added a new chapter to the Users Guide about Uniform
+ Resource Identifiers and their handling with the new API.</p>
+ <p>
+ Added two new API functions:
+ uri_string:allowed_characters/0 and
+ uri_string:percent_decode/1.</p>
+ <p>
+ This change has been marked as potentially incompatible
+ as uri_string:normalize/2 used to decode percent-encoded
+ character triplets that corresponded to characters not in
+ the reserved set. After this change,
+ uri_string:normalize/2 will only decode those
+ percent-encoded triplets that correspond to characters in
+ the unreserved set (ALPHA / DIGIT / "-" / "." / "_" /
+ "~").</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16460</p>
+ </item>
+ <item>
+ <p>
+ The <c>shell_docs</c> module has been expanded with the
+ possibility to configure unicode, ansi and column size
+ for the rendered text.</p>
+ <p>
+ Own Id: OTP-16990</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>STDLIB 3.13.2</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/stdlib/doc/src/part.xml b/lib/stdlib/doc/src/part.xml
index 93c47405bf..b6a2f16b57 100644
--- a/lib/stdlib/doc/src/part.xml
+++ b/lib/stdlib/doc/src/part.xml
@@ -37,5 +37,6 @@
<xi:include href="introduction.xml"/>
<xi:include href="io_protocol.xml"/>
<xi:include href="unicode_usage.xml"/>
+ <xi:include href="uri_string_usage.xml"/>
</part>
diff --git a/lib/stdlib/doc/src/shell_docs.xml b/lib/stdlib/doc/src/shell_docs.xml
index c8fba1b43e..bd3e3600f7 100644
--- a/lib/stdlib/doc/src/shell_docs.xml
+++ b/lib/stdlib/doc/src/shell_docs.xml
@@ -46,6 +46,44 @@
<datatypes>
<datatype>
<name name="docs_v1"/>
+ <desc>
+ <p>
+ The record holding EEP-48 documentation for a module.
+ You can use <seemfa marker="kernel:code#get_doc/1">code:get_doc/1</seemfa>
+ to fetch this information from a module.
+ </p>
+ </desc>
+ </datatype>
+ <datatype>
+ <name name="config"/>
+ <desc>
+ <p>
+ The configuration of how the documentation should be rendered.
+ </p>
+ <taglist>
+ <tag>encoding</tag>
+ <item>
+ Configure the encoding that should be used by
+ the renderer for graphical details such as bullet-points.
+ By default <c>shell_docs</c> uses the value returned
+ by <seemfa marker="io#getopts/0"><c>io:getopts()</c></seemfa>.</item>
+ <tag>ansi</tag>
+ <item>
+ Configure whether <url href="https://en.wikipedia.org/wiki/ANSI_escape_code">
+ ansi escape codes</url> should be used to
+ render graphical details such as bold and underscore. By default
+ <c>shell_docs</c> will try to determine if the receiving shell
+ supports ansi escape codes. It is possible to override
+ the automated check by setting the kernel configuration parameter
+ <c>shell_docs_ansi</c> to a <c>boolean()</c> value.</item>
+ <tag>columns</tag>
+ <item>
+ Configure how wide the target documentation should be rendered.
+ By default <c>shell_docs</c> used the value returned by
+ <seemfa marker="io#columns/0"><c>io:columns()</c></seemfa>.
+ </item>
+ </taglist>
+ </desc>
</datatype>
<datatype>
<name name="chunk_element_block_type"/>
@@ -71,29 +109,23 @@
<func>
<name name="render" arity="2" since="OTP 23.0"/>
- <fsummary>Render the documentation for a module.</fsummary>
- <desc>
- <p>Render the documentation for a module.</p>
- </desc>
- </func>
- <func>
- <name name="render" arity="3" since="OTP 23.0"/>
- <name name="render" arity="4" since="OTP 23.0"/>
- <fsummary>Render the documentation for a function.</fsummary>
+ <name name="render" arity="3" clause_i="1" since="OTP 23.2"/>
+ <name name="render" arity="3" clause_i="2" since="OTP 23.0"/>
+ <name name="render" arity="4" clause_i="1" since="OTP 23.2"/>
+ <name name="render" arity="4" clause_i="2" since="OTP 23.0"/>
+ <name name="render" arity="5" since="OTP 23.2"/>
+ <fsummary>Render the documentation for a module or function.</fsummary>
<desc>
- <p>Render the documentation for a function.</p>
+ <p>Render the documentation for a module or function.</p>
</desc>
</func>
<func>
<name name="render_type" arity="2" since="OTP 23.0"/>
- <fsummary>Render a list of all available types in a module.</fsummary>
- <desc>
- <p>Render a list of all available types in a module.</p>
- </desc>
- </func>
- <func>
- <name name="render_type" arity="3" since="OTP 23.0"/>
- <name name="render_type" arity="4" since="OTP 23.0"/>
+ <name name="render_type" arity="3" clause_i="1" since="OTP 23.2"/>
+ <name name="render_type" arity="3" clause_i="2" since="OTP 23.0"/>
+ <name name="render_type" arity="4" clause_i="1" since="OTP 23.2"/>
+ <name name="render_type" arity="4" clause_i="2" since="OTP 23.0"/>
+ <name name="render_type" arity="5" since="OTP 23.2"/>
<fsummary>Render the documentation of a type in a module.</fsummary>
<desc>
<p>Render the documentation of a type in a module.</p>
@@ -101,14 +133,11 @@
</func>
<func>
<name name="render_callback" arity="2" since="OTP 23.0"/>
- <fsummary>Render a list of all available callbacks in a module.</fsummary>
- <desc>
- <p>Render a list of all available callbacks in a module.</p>
- </desc>
- </func>
- <func>
- <name name="render_callback" arity="3" since="OTP 23.0"/>
- <name name="render_callback" arity="4" since="OTP 23.0"/>
+ <name name="render_callback" arity="3" clause_i="1" since="OTP 23.2"/>
+ <name name="render_callback" arity="3" clause_i="2" since="OTP 23.0"/>
+ <name name="render_callback" arity="4" clause_i="1" since="OTP 23.2"/>
+ <name name="render_callback" arity="4" clause_i="2" since="OTP 23.0"/>
+ <name name="render_callback" arity="5" since="OTP 23.2"/>
<fsummary>Render the documentation of a callback in a module.</fsummary>
<desc>
<p>Render the documentation of a callback in a module.</p>
diff --git a/lib/stdlib/doc/src/supervisor.xml b/lib/stdlib/doc/src/supervisor.xml
index 3ce5094e43..6a44219084 100644
--- a/lib/stdlib/doc/src/supervisor.xml
+++ b/lib/stdlib/doc/src/supervisor.xml
@@ -641,7 +641,7 @@ child_spec() = #{id => child_id(), % mandatory
specifications and child processes belonging to
supervisor <c><anno>SupRef</anno></c>.</p>
<p>Notice that calling this function when supervising many
- childrens under low memory conditions can cause an
+ children under low memory conditions can cause an
out of memory exception.</p>
<p>For a description of <c><anno>SupRef</anno></c>, see
<seeerl marker="#SupRef"><c>start_child/2</c></seeerl>.</p>
diff --git a/lib/stdlib/doc/src/uri_string.xml b/lib/stdlib/doc/src/uri_string.xml
index a792decbff..dea8e60979 100644
--- a/lib/stdlib/doc/src/uri_string.xml
+++ b/lib/stdlib/doc/src/uri_string.xml
@@ -84,6 +84,9 @@
<item>Dissecting form-urlencoded query strings into a list of key-value pairs<br></br>
<seemfa marker="#dissect_query/1"><c>dissect_query/1</c></seemfa>
</item>
+ <item>Decoding percent-encoded triplets<br></br>
+ <seemfa marker="#percent_decode/1"><c>percent_decode/1</c></seemfa>
+ </item>
</list>
<p>There are four different encodings present during the handling of URIs:</p>
<list type="bulleted">
@@ -150,6 +153,21 @@
<funcs>
<func>
+ <name name="allowed_characters" arity="0" since="OTP 23.2"/>
+ <fsummary>Print allowed characters in URI components.</fsummary>
+ <desc>
+ <p>This is a utility function meant to be used in the shell for printing
+ the allowed characters in each
+ major URI component, and also in the most important characters sets.
+ Please note that this function does not replace the ABNF rules defined by
+ the standards, these character sets are derived directly from those
+ aformentioned rules. For more information see the
+ <seeguide marker="uri_string_usage#percent_encoding">Uniform Resource
+ Identifiers</seeguide> chapter in stdlib's Users Guide.</p>
+ </desc>
+ </func>
+
+ <func>
<name name="compose_query" arity="1" since="OTP 21.0"/>
<fsummary>Compose urlencoded query string.</fsummary>
<desc>
@@ -309,6 +327,37 @@
</func>
<func>
+ <name name="percent_decode" arity="1" since="OTP 23.2"/>
+ <fsummary>Decode percent-decode triplets in the input.</fsummary>
+ <desc>
+ <p>Decodes all percent-encoded triplets in the input that can be both a
+ <c>uri_string()</c> and a <c>uri_map()</c>. Note, that this function performs
+ raw decoding and it shall be used on already parsed URI components. Applying
+ this function directly on a standard URI can effectively change it.</p>
+ <p>If the input encoding is not UTF-8, an error tuple is returned.</p>
+ <p><em>Example:</em></p>
+ <pre>
+1> <input>uri_string:percent_decode(#{host => "localhost-%C3%B6rebro",path => [],</input>
+1> <input>scheme => "http"}).</input>
+#{host => "localhost-örebro",path => [],scheme => "http"}
+2> <![CDATA[uri_string:percent_decode(<<"%C3%B6rebro">>).]]>
+<![CDATA[<<"örebro"/utf8>>]]>
+ </pre>
+ <warning><p>
+ Using <c>uri_string:percent_decode/1</c> directly on a URI is not safe. This
+ example shows, that after each consecutive application of the function
+ the resulting URI will be changed. None of these URIs refer to the same
+ resource.</p>
+ <pre>
+<![CDATA[3> uri_string:percent_decode(<<"http://local%252Fhost/path">>).
+<<"http://local%2Fhost/path">>
+4> uri_string:percent_decode(<<"http://local%2Fhost/path">>).
+<<"http://local/host/path">>]]>
+ </pre></warning>
+ </desc>
+ </func>
+
+ <func>
<name name="recompose" arity="1" since="OTP 21.0"/>
<fsummary>Recompose URI.</fsummary>
<desc>
diff --git a/lib/stdlib/doc/src/uri_string_usage.xml b/lib/stdlib/doc/src/uri_string_usage.xml
new file mode 100644
index 0000000000..72851096b7
--- /dev/null
+++ b/lib/stdlib/doc/src/uri_string_usage.xml
@@ -0,0 +1,370 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>2020</year>
+ <year>2020</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ </legalnotice>
+
+ <title>Uniform Resource Identifiers</title>
+ <prepared>Péter Dimitrov</prepared>
+ <responsible></responsible>
+ <docno></docno>
+ <approved></approved>
+ <checked></checked>
+ <date>2020-09-30</date>
+ <rev>PA1</rev>
+ <file>uri_string_usage.xml</file>
+ </header>
+ <section>
+ <title>Basics</title>
+ <p>At the time of writing this document, in October 2020, there are
+ two major standards concerning Universal Resource Identifiers and
+ Universal Resource Locators:</p>
+ <list type="bulleted">
+ <item><p>
+ <url href="https://www.ietf.org/rfc/rfc3986.txt">RFC 3986 - Uniform Resource
+ Identifier (URI): Generic Syntax</url></p></item>
+ <item><p>
+ <url href="https://url.spec.whatwg.org/">WHAT WG URL - Living standard</url>
+ </p></item>
+ </list>
+ <p>
+ The former is a classical standard with a proper formal syntax, using the so
+ called <url href="https://www.ietf.org/rfc/rfc2234.txt">Augmented Backus-Naur Form
+ (ABNF)</url> for describing
+ the grammar, while the latter is a living document describing the current pratice,
+ that is, how a majority of Web browsers work with URIs. WHAT WG URL is Web focused
+ and it has no formal grammar but a plain english description of the algorithms
+ that should be followed.</p>
+ <p>What is the difference between them, if any? They provide an overlapping
+ definition for resource identifiers and they are not compatible.
+ The <seeerl marker="stdlib:uri_string"><c>uri_string</c></seeerl> module implements
+ <url href="https://www.ietf.org/rfc/rfc3986.txt">RFC 3986</url> and the term URI will
+ be used throughout this document. A URI is an identifier, a string of characters
+ that identifies a particular resource.</p>
+ <p>
+ For a more complete problem
+ statement regarding the URIs check the
+ <url href="https://tools.ietf.org/html/draft-ruby-url-problem-01">URL Problem
+ Statement and Directions</url>.</p>
+ </section>
+
+ <section>
+ <title>What is a URI?</title>
+ <p>Let's start with what it is not. It is not the text that you type in the address
+ bar in your Web browser. Web browsers do all possible heuristics to convert the
+ input into a valid URI that could be sent over the network.</p>
+ <p>A URI is an identifier consisting of a sequence of characters matching the syntax
+ rule named <c>URI</c> in
+ <url href="https://www.ietf.org/rfc/rfc3986.txt">RFC 3986</url>.
+ </p>
+ <p>It is crucial to clarify that a <i>character</i> is a symbol that is displayed on
+ a terminal or written to paper and should not be confused with its internal
+ representation.</p>
+ <p>A URI more specifically, is a sequence of characters from a
+ subset of the US ASCII character set. The generic URI syntax consists of a
+ hierarchical sequence of components referred to as the scheme, authority,
+ path, query, and fragment. There is a formal description for
+ each of these components in
+ <url href="https://www.ietf.org/rfc/rfc2234.txt">ABNF</url> notation in
+ <url href="https://www.ietf.org/rfc/rfc3986.txt">RFC 3986</url>:</p>
+ <pre>
+ URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
+ hier-part = "//" authority path-abempty
+ / path-absolute
+ / path-rootless
+ / path-empty
+ scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
+ authority = [ userinfo "@" ] host [ ":" port ]
+ userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
+
+ reserved = gen-delims / sub-delims
+ gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
+ sub-delims = "!" / "$" / "&amp;" / "'" / "(" / ")"
+ / "*" / "+" / "," / ";" / "="
+
+ unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
+ </pre>
+ </section>
+
+ <section>
+ <title>The uri_string module</title>
+ <p>As producing and consuming standard URIs can get quite complex, Erlang/OTP
+ provides
+ a module, <seeerl marker="stdlib:uri_string"><c>uri_string</c></seeerl>, to handle all the most difficult operations such as parsing,
+ recomposing, normalizing and resolving URIs against a base URI.
+ </p>
+ <p>The API functions in <seeerl marker="stdlib:uri_string"><c>uri_string</c></seeerl>
+ work on two basic data types
+ <seetype marker="uri_string#uri_string"><c>uri_string()</c></seetype> and
+ <seetype marker="uri_string#uri_map"><c>uri_map()</c></seetype>.
+ <seetype marker="uri_string#uri_string"><c>uri_string()</c></seetype> represents a
+ standard URI, while
+ <seetype marker="uri_string#uri_map"><c>uri_map()</c></seetype> is a wider datatype,
+ that can represent URI components using
+ <seeguide marker="unicode_usage#what-unicode-is">Unicode</seeguide> characters.
+ <seetype marker="uri_string#uri_map"><c>uri_map()</c></seetype>
+ is a convenient choice for enabling
+ operations such as producing standard compliant URIs out of components that have
+ special or <seeguide marker="unicode_usage#what-unicode-is">Unicode</seeguide>
+ characters. It is easier to explain this by an example.
+ </p>
+ <p>Let's say that we would like to create the following URI and send it over the
+ network: <c>http://cities/örebro?foo bar</c>. This is not a valid URI as it contains
+ characters that are not allowed in a URI such as "ö" and the space. We can verify
+ this by parsing the URI:
+ </p>
+ <pre>
+ 1> uri_string:parse("http://cities/örebro?foo bar").
+ {error,invalid_uri,":"}
+ </pre>
+ <p>The URI parser tries all possible combinations to interpret the input and fails
+ at the last attempt when it encounters the colon character <c>":"</c>. Note, that
+ the inital fault occurs when the parser attempts to interpret the character
+ <c>"ö"</c> and after a failure back-tracks to the point where it has another
+ possible parsing alternative.</p>
+ <p>The proper way to solve this problem is to use
+ <seemfa marker="uri_string#recompose/1"><c>uri_string:recompose/1</c></seemfa>
+ with a <seetype marker="uri_string#uri_map"><c>uri_map()</c></seetype> as input:</p>
+ <pre>
+ 2> uri_string:recompose(#{scheme => "http", host => "cities", path => "/örebro",
+ query => "foo bar"}).
+ "http://cities/%C3%B6rebro?foo%20bar"
+ </pre>
+ <p>The result is a valid URI where all the special characters are encoded as defined
+ by the standard. Applying
+ <seemfa marker="uri_string#parse/1"><c>uri_string:parse/1</c></seemfa> and
+ <seemfa marker="uri_string#percent_decode/1"><c>uri_string:percent_decode/1</c></seemfa>
+ on the URI returns the original input:
+ </p>
+ <pre>
+ 3> uri_string:percent_decode(uri_string:parse("http://cities/%C3%B6rebro?foo%20bar")).
+ #{host => "cities",path => "/örebro",query => "foo bar",
+ scheme => "http"}
+ </pre>
+ <p>This symmetric property is heavily used in our property test suite.
+ </p>
+ </section>
+
+ <section>
+ <title>Percent-encoding</title>
+ <p>As you have seen in the previous chapter, a standard URI can only contain a strict
+ subset of the US ASCII character set, moreover the allowed set of characters is not
+ the same in the different URI components. Percent-encoding is a mechanism to
+ represent a data octet in a component when that octet's corresponding character
+ is outside of
+ the allowed set or is being used as a delimiter. This is what you see when <c>"ö"</c>
+ is encoded as <c>%C3%B6</c> and <c>space</c> as <c>%20</c>.
+ Most of the API functions are
+ expecting UTF-8 encoding when handling percent-encoded triplets. The UTF-8 encoding
+ of the <seeguide marker="unicode_usage#what-unicode-is">Unicode</seeguide>
+ character <c>"ö"</c> is two octets: <c>OxC3 0xB6</c>.
+ The character <c>space</c> is in the first 128 characters of
+ <seeguide marker="unicode_usage#what-unicode-is">Unicode</seeguide> and it is encoded
+ using a single octet <c>0x20</c>.</p>
+ <note><p><seeguide marker="unicode_usage#what-unicode-is">Unicode</seeguide>
+ is backward compatible with ASCII, the encoding of the first 128
+ characters is the same binary value as in ASCII.
+ </p></note>
+ <p><marker id="percent_encoding"></marker>
+ It is a major source of confusion exactly which characters will be
+ percent-encoded. In order to make it easier to answer this question the library
+ provides a utility function,
+ <seemfa marker="uri_string#allowed_characters/0"><c>uri_string:allowed_characters/0
+ </c></seemfa>,
+ that lists the allowed set of characters in each major
+ URI component, and also in the most important standard character sets.
+ </p>
+ <pre>
+ 1> uri_string:allowed_characters().
+ <![CDATA[{scheme,
+ "+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"},
+ {userinfo,
+ "!$%&'()*+,-.0123456789:;=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~"},
+ {host,
+ "!$&'()*+,-.0123456789:;=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~"},
+ {ipv4,".0123456789"},
+ {ipv6,".0123456789:ABCDEFabcdef"},
+ {regname,
+ "!$%&'()*+,-.0123456789;=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~"},
+ {path,
+ "!$%&'()*+,-./0123456789:;=@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~"},
+ {query,
+ "!$%&'()*+,-./0123456789:;=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~"},
+ {fragment,
+ "!$%&'()*+,-./0123456789:;=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~"},
+ {reserved,"!#$&'()*+,/:;=?@[]"},
+ {unreserved,
+ "-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~"}] ]]>
+ </pre>
+ <p>If a URI component has a character that is not allowed, it will be
+ percent-encoded when the URI is produced:
+ </p>
+ <pre>
+ 2> uri_string:recompose(#{scheme => "https", host => "local#host", path => ""}).
+ "https://local%23host"
+ </pre>
+ <p>Consuming a URI containing percent-encoded triplets can take many steps. The
+ following example shows how to handle an input URI that is not normalized and
+ contains multiple percent-encoded triplets.
+ First, the input <seetype marker="uri_string#uri_string"><c>uri_string()</c></seetype>
+ is to be parsed into a <seetype marker="uri_string#uri_map"><c>uri_map()</c></seetype>.
+ The parsing only splits the URI into its components without doing any decoding:
+ </p>
+ <pre>
+ 3> uri_string:parse("http://%6C%6Fcal%23host/%F6re%26bro%20").
+ #{host => "%6C%6Fcal%23host",path => "/%F6re%26bro%20",
+ scheme => "http"}}
+ </pre>
+ <p>The input is a valid URI but how can you decode those
+ percent-encoded octets? You can try to normalize the input with
+ <seemfa marker="uri_string#normalize/1"><c>uri_string:normalize/1</c></seemfa>. The
+ normalize operation decodes those
+ percent-encoded triplets that correspond to a character in the unreserved set.
+ Normalization is a safe, idempotent operation that converts a URI into its
+ canonical form:</p>
+ <pre>
+ 4> uri_string:normalize("http://%6C%6Fcal%23host/%F6re%26bro%20").
+ "http://local%23host/%F6re%26bro%20"
+ 5> uri_string:normalize("http://%6C%6Fcal%23host/%F6re%26bro%20", [return_map]).
+ #{host => "local%23host",path => "/%F6re%26bro%20",
+ scheme => "http"}
+ </pre>
+ <p>There are still a few percent-encoded triplets left in the output. At this point,
+ when the URI is already parsed, it is safe to apply application specific decoding on
+ the remaining character triplets. Erlang/OTP provides a function,
+ <seemfa marker="uri_string#percent_decode/1"><c>uri_string:percent_decode/1</c></seemfa>
+ for raw percent decoding
+ that you can use on the host and path components, or on the whole map:
+ </p>
+ <pre>
+ 6> uri_string:percent_decode("local%23host").
+ "local#host"
+ 7> uri_string:percent_decode("/%F6re%26bro%20").
+ <![CDATA[{error,invalid_utf8,<<"/öre&bro ">>}]]>
+ 8> uri_string:percent_decode(#{host => "local%23host",path => "/%F6re%26bro%20",
+ scheme => "http"}).
+ <![CDATA[{error,{invalid,{path,{invalid_utf8,<<"/öre&bro ">>}}}}]]>
+ </pre>
+ <p>The <c>host</c> was successfully decoded but the path contains at least one
+ character with
+ non-UTF-8 encoding. In order to be able to decode this, you have to make assumptions
+ about the encoding used in these triplets. The most obvious choice is
+ <i>latin-1</i>, so you can try
+ <seemfa marker="uri_string#transcode/2"><c>uri_string:transcode/2</c></seemfa>, to
+ transcode the path to UTF-8 and run the percent-decode operation on the
+ transcoded string:
+ </p>
+ <pre>
+ 9> uri_string:transcode("/%F6re%26bro%20", [{in_encoding, latin1}]).
+ "/%C3%B6re%26bro%20"
+ 10> uri_string:percent_decode("/%C3%B6re%26bro%20").
+ <![CDATA["/öre&bro "]]>
+ </pre>
+ <p>It is important to emphasize that it is not safe to apply
+ <seemfa marker="uri_string#percent_decode/1"><c>uri_string:percent_decode/1</c></seemfa>
+ directly on an input URI:
+ </p>
+ <pre>
+ 11> uri_string:percent_decode("http://%6C%6Fcal%23host/%C3%B6re%26bro%20").
+ <![CDATA["http://local#host/öre&bro "
+ 12> uri_string:parse("http://local#host/öre&bro ").]]>
+ {error,invalid_uri,":"}
+ </pre>
+ <note><p>Percent-encoding is implemented in
+ <seemfa marker="uri_string#recompose/1"><c>uri_string:recompose/1</c></seemfa>
+ and it happens when converting a
+ <seetype marker="uri_string#uri_map"><c>uri_map()</c></seetype>
+ into a <seetype marker="uri_string#uri_string"><c>uri_string()</c></seetype>.
+ There is no equivalent to a raw percent-encoding function as percent-encoding
+ shall be applied on the component level using different sets of allowed characters.
+ Applying percent-encoding directly on an input URI would not be safe just as in
+ the case of
+ <seemfa marker="uri_string#percent_decode/1"><c>uri_string:percent_decode/1</c></seemfa>,
+ the output could be an invalid URI.
+ </p>
+ </note>
+ </section>
+
+ <section>
+ <title>Normalization</title>
+ <p>Normalization is the operation of converting the input URI into a <i>canonical</i>
+ form and keeping the reference to the same underlying resource. The most common
+ application of normalization is determining whether two URIs are equivalent
+ without accessing their referenced resources.</p>
+ <p>Normalization has 6 distinct steps. First the input URI is parsed into an
+ intermediate form that can handle
+ <seeguide marker="unicode_usage#what-unicode-is">Unicode</seeguide> characters.
+ This datatype is the
+ <seetype marker="uri_string#uri_map"><c>uri_map()</c></seetype>, that can hold the
+ components of the URI in map elements of type
+ <seetype marker="unicode#chardata"><c>unicode:chardata()</c></seetype>.
+ After having the intermediate form, a sequence of
+ normalization algorithms are applied to the individual URI components:</p>
+ <taglist>
+ <tag>Case normalization</tag>
+ <item>
+ <p>Converts the <c>scheme</c> and <c>host</c> components
+ to lower case as they are not case sensitive.</p>
+ </item>
+ <tag>Percent-encoding normalization</tag>
+ <item>
+ <p>Decodes percent-encoded triplets that
+ correspond to characters in the unreserved set.</p>
+ </item>
+ <tag>Scheme-based normalization</tag>
+ <item>
+ <p>Applying rules for the schemes http, https,
+ ftp, ssh, sftp and tftp.</p>
+ </item>
+ <tag>Path segment normalization</tag>
+ <item>
+ <p>Converts the path into a canonical form.</p>
+ </item>
+ </taglist>
+ <p>After these steps, the intermediate data structure, an
+ <seetype marker="uri_string#uri_map"><c>uri_map()</c></seetype>,
+ is fully normalized. The last step is applying
+ <seemfa marker="uri_string#recompose/1"><c>uri_string:recompose/1</c></seemfa>
+ that converts the intermediate structure into a valid canonical URI string.</p>
+ <p>Notice the order, the
+ <seemfa marker="uri_string#normalize/2"><c>uri_string:normalize(URIMap, [return_map])</c></seemfa> that we
+ used many times in this user guide is a shortcut in the normalization process
+ returning the intermediate datastructure, and allowing us to inspect and apply
+ further decoding on the remaining percent-encoded triplets.</p>
+ <pre>
+ 13> uri_string:normalize("hTTp://LocalHost:80/%c3%B6rebro/a/../b").
+ "http://localhost/%C3%B6rebro/b"
+ 14> uri_string:normalize("hTTp://LocalHost:80/%c3%B6rebro/a/../b", [return_map]).
+ #{host => "localhost",path => "/%C3%B6rebro/b",
+ scheme => "http"}
+ </pre>
+ </section>
+
+ <section>
+ <title>Special considerations</title>
+ <p>The current URI implementation provides support for producing and consuming
+ standard URIs. The API is not meant to be directly exposed in a Web
+ browser's address bar where users can basically enter free text. Application
+ designers shall implement proper heuristics to map the input into a parsable URI.</p>
+ </section>
+
+</chapter>
diff --git a/lib/stdlib/src/gen_server.erl b/lib/stdlib/src/gen_server.erl
index e49961a5f0..3638027d4f 100644
--- a/lib/stdlib/src/gen_server.erl
+++ b/lib/stdlib/src/gen_server.erl
@@ -491,6 +491,15 @@ do_send(Dest, Msg) ->
end,
ok.
+do_multi_call([Node], Name, Req, infinity) when Node =:= node() ->
+ % Special case when multi_call is used with local node only.
+ % In that case we can leverage the benefit of recv_mark optimisation
+ % existing in simple gen:call.
+ try gen:call(Name, '$gen_call', Req, infinity) of
+ {ok, Res} -> {[{Node, Res}],[]}
+ catch exit:_ ->
+ {[], [Node]}
+ end;
do_multi_call(Nodes, Name, Req, infinity) ->
Tag = make_ref(),
Monitors = send_nodes(Nodes, Name, Tag, Req),
diff --git a/lib/stdlib/src/otp_internal.erl b/lib/stdlib/src/otp_internal.erl
index b6531d9b5c..1eca1c29c3 100644
--- a/lib/stdlib/src/otp_internal.erl
+++ b/lib/stdlib/src/otp_internal.erl
@@ -60,11 +60,21 @@ obsolete(crypto, hmac_update, 2) ->
obsolete(crypto, poly1305, 2) ->
{deprecated, "use crypto:mac/3 instead", "OTP 24"};
obsolete(crypto, rand_uniform, 2) ->
- {deprecated, "use rand:rand_uniform/1 instead"};
+ {deprecated, "use rand:uniform/1 instead"};
obsolete(crypto, stream_decrypt, 2) ->
{deprecated, "use crypto:crypto_update/2 instead", "OTP 24"};
obsolete(crypto, stream_encrypt, 2) ->
{deprecated, "use crypto:crypto_update/2 instead", "OTP 24"};
+obsolete(erl_tidy, dir, 0) ->
+ {deprecated, "use https://github.com/richcarl/erl_tidy", "OTP 24"};
+obsolete(erl_tidy, dir, 1) ->
+ {deprecated, "use https://github.com/richcarl/erl_tidy", "OTP 24"};
+obsolete(erl_tidy, file, 1) ->
+ {deprecated, "use https://github.com/richcarl/erl_tidy", "OTP 24"};
+obsolete(erl_tidy, module, 1) ->
+ {deprecated, "use https://github.com/richcarl/erl_tidy", "OTP 24"};
+obsolete(erl_tidy, module, 2) ->
+ {deprecated, "use https://github.com/richcarl/erl_tidy", "OTP 24"};
obsolete(erlang, get_stacktrace, 0) ->
{deprecated, "use the new try/catch syntax for retrieving the stack backtrace", "OTP 24"};
obsolete(erlang, now, 0) ->
@@ -102,13 +112,13 @@ obsolete(queue, lait, 1) ->
obsolete(snmp, add_agent_caps, 2) ->
{deprecated, "use snmpa:add_agent_caps/2 instead.", "OTP 24"};
obsolete(snmp, c, 1) ->
- {deprecated, "use snmpa:c/1 instead.", "OTP 24"};
+ {deprecated, "use snmpc:compile/1 instead.", "OTP 24"};
obsolete(snmp, c, 2) ->
- {deprecated, "use snmpa:c/2 instead.", "OTP 24"};
+ {deprecated, "use snmpc:compile/2 instead.", "OTP 24"};
obsolete(snmp, change_log_size, 1) ->
{deprecated, "use snmpa:change_log_size/1 instead.", "OTP 24"};
obsolete(snmp, compile, 3) ->
- {deprecated, "use snmpa:compile/3 instead.", "OTP 24"};
+ {deprecated, "use snmpc:compile/3 instead.", "OTP 24"};
obsolete(snmp, current_address, 0) ->
{deprecated, "use snmpa:current_address/0 instead.", "OTP 24"};
obsolete(snmp, current_community, 0) ->
@@ -142,7 +152,7 @@ obsolete(snmp, int_to_enum, 2) ->
obsolete(snmp, int_to_enum, 3) ->
{deprecated, "use snmpa:int_to_enum/3 instead.", "OTP 24"};
obsolete(snmp, is_consistent, 1) ->
- {deprecated, "use snmpa:is_consistent/1 instead.", "OTP 24"};
+ {deprecated, "use snmpc:is_consistent/1 instead.", "OTP 24"};
obsolete(snmp, load_mibs, 2) ->
{deprecated, "use snmpa:load_mibs/2 instead.", "OTP 24"};
obsolete(snmp, log_to_txt, 2) ->
@@ -152,7 +162,7 @@ obsolete(snmp, log_to_txt, 3) ->
obsolete(snmp, log_to_txt, 4) ->
{deprecated, "use snmpa:log_to_txt/4 instead.", "OTP 24"};
obsolete(snmp, mib_to_hrl, 1) ->
- {deprecated, "use snmpa:mib_to_hrl/1 instead.", "OTP 24"};
+ {deprecated, "use snmpc:mib_to_hrl/1 instead.", "OTP 24"};
obsolete(snmp, name_to_oid, 1) ->
{deprecated, "use snmpa:name_to_oid/1 instead.", "OTP 24"};
obsolete(snmp, name_to_oid, 2) ->
@@ -489,6 +499,8 @@ obsolete(erl_scan, token_info, _) ->
{removed, "erl_scan:{category,column,line,location,symbol,text}/1 instead"};
obsolete(gen_fsm, _, _) ->
{deprecated, "use the 'gen_statem' module instead"};
+obsolete(igor, _, _) ->
+ {deprecated, "use https://github.com/richcarl/igor", "OTP 24"};
obsolete(pg2, _, _) ->
{deprecated, "use 'pg' instead", "OTP 24"};
obsolete(random, _, _) ->
diff --git a/lib/stdlib/src/shell_docs.erl b/lib/stdlib/src/shell_docs.erl
index ac46dc1e04..5abb3a7f0c 100644
--- a/lib/stdlib/src/shell_docs.erl
+++ b/lib/stdlib/src/shell_docs.erl
@@ -21,9 +21,9 @@
-include_lib("kernel/include/eep48.hrl").
--export([render/2, render/3, render/4]).
--export([render_type/2, render_type/3, render_type/4]).
--export([render_callback/2, render_callback/3, render_callback/4]).
+-export([render/2, render/3, render/4, render/5]).
+-export([render_type/2, render_type/3, render_type/4, render_type/5]).
+-export([render_callback/2, render_callback/3, render_callback/4, render_callback/5]).
%% Used by chunks.escript in erl_docgen
-export([validate/1, normalize/1]).
@@ -32,8 +32,10 @@
-export([get_doc/1, get_doc/3, get_type_doc/3, get_callback_doc/3]).
-record(config, { docs,
+ encoding,
+ ansi,
io_opts = io:getopts(),
- io_columns = element(2,io:columns())
+ columns
}).
-define(ALL_ELEMENTS,[a,p,'div',br,h1,h2,h3,i,em,pre,code,ul,ol,li,dl,dt,dd]).
@@ -49,6 +51,9 @@
%% If you update the below types, make sure to update the documentation in
%% erl_docgen/doc/src/doc_storage.xml as well!!!
-type docs_v1() :: #docs_v1{}.
+-type config() :: #{ encoding => unicode | latin1,
+ columns => pos_integer(),
+ ansi => boolean() }.
-type chunk_elements() :: [chunk_element()].
-type chunk_element() :: {chunk_element_type(),chunk_element_attrs(),
chunk_elements()} | binary().
@@ -233,7 +238,9 @@ trim_inline([Bin|T],true) when is_binary(Bin) ->
trim_inline([{Elem,Attr,Content}|T],TrimSpace) ->
{NewContent,ContentTrimSpace} = trim_inline(Content,TrimSpace),
{NewT,TTrimSpace} = trim_inline(T,ContentTrimSpace),
- if NewContent == [] ->
+ IsAnchor = (Elem =:= a) andalso proplists:is_defined(id,Attr),
+ if NewContent == [] andalso (not IsAnchor) ->
+ %% Remove if all content has been trimmed and this is not an anchor
{NewT, TTrimSpace};
true ->
{[{Elem,Attr,NewContent} | NewT], TTrimSpace}
@@ -246,13 +253,11 @@ trim_inline([],TrimSpace) ->
%% This is complicated by the fact that the first or last element
%% may not have any binary, or have the binary deeply nested within.
trim_first_and_last(Content, What) when What < 256 ->
- {NewContent,_State} = trim_last(trim_first(Content,What),What),
- NewContent.
+ {FirstTrimmed, _} = trim_first(Content,What),
+ {LastTrimmed, _} = trim_last(FirstTrimmed,What),
+ LastTrimmed.
-trim_first(Content,What) ->
- {NewContent,_State} = trim_first(Content,false,What),
- NewContent.
-trim_first([Bin|T],false,What) when is_binary(Bin) ->
+trim_first([Bin|T],What) when is_binary(Bin) ->
case Bin of
<<What>> ->
{T,true};
@@ -261,17 +266,17 @@ trim_first([Bin|T],false,What) when is_binary(Bin) ->
Bin ->
{[Bin|T],true}
end;
-trim_first([{Elem,Attr,Content} = Tag|T],false,What) ->
- case trim_first(Content,false,What) of
+trim_first([{Elem,Attr,Content} = Tag|T],What) ->
+ case trim_first(Content,What) of
{[],true} ->
{T,true};
{NewContent,true} ->
{[{Elem,Attr,NewContent}|T],true};
{Content,false} ->
- {NewT,NewState} = trim_first(T,false,What),
+ {NewT,NewState} = trim_first(T,What),
{[Tag | NewT],NewState}
end;
-trim_first([],false,_What) ->
+trim_first([],_What) ->
{[],false}.
trim_last([Bin | T],What) when is_binary(Bin) ->
@@ -294,14 +299,17 @@ trim_last([{Elem,Attr,Content} = Tag|T],What) ->
{[Tag | NewT],true};
{T,false} ->
case trim_last(Content,What) of
- {[],NewState} ->
- {T,NewState};
+ {[],true} ->
+ %% If the content became empty and we processed some text
+ %% we remove the element.
+ {[],true};
{NewContent,NewState} ->
{[{Elem,Attr,NewContent}|T],NewState}
end
end;
trim_last([],_What) ->
{[],false}.
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% API function for dealing with the function documentation
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -328,29 +336,68 @@ get_doc(Module, Function, Arity) ->
[{F,A,S,get_local_doc({F,A},D),M} || {F,A,S,D,M} <- FnFunctions].
--spec render(Module :: module(), Docs :: docs_v1()) -> unicode:chardata().
-render(Module, #docs_v1{ module_doc = ModuleDoc } = D) ->
+-spec render(Module, Docs) -> unicode:chardata() when
+ Module :: module(),
+ Docs :: docs_v1().
+render(Module, #docs_v1{ } = D) when is_atom(Module) ->
+ render(Module, D, #{}).
+
+-spec render(Module, Docs, Config) -> unicode:chardata() when
+ Module :: module(),
+ Docs :: docs_v1(),
+ Config :: config();
+
+ (Module, Function, Docs) -> Res when
+ Module :: module(),
+ Function :: atom(),
+ Docs :: docs_v1(),
+ Res :: unicode:chardata() | {error,function_missing}.
+render(Module, #docs_v1{ module_doc = ModuleDoc } = D, Config)
+ when is_atom(Module), is_map(Config) ->
render_headers_and_docs([[{h2,[],[<<"\t",(atom_to_binary(Module))/binary>>]}]],
- get_local_doc(Module, ModuleDoc), D).
+ get_local_doc(Module, ModuleDoc), D, Config);
+render(_Module, Function, #docs_v1{ } = D) ->
+ render(_Module, Function, D, #{}).
--spec render(Module :: module(), Function :: atom(), Docs :: docs_v1()) ->
- unicode:chardata() | {error,function_missing}.
-render(_Module, Function, #docs_v1{ docs = Docs } = D) ->
+-spec render(Module, Function, Docs, Config) -> Res when
+ Module :: module(),
+ Function :: atom(),
+ Docs :: docs_v1(),
+ Config :: config(),
+ Res :: unicode:chardata() | {error,function_missing};
+
+ (Module, Function, Arity, Docs) -> Res when
+ Module :: module(),
+ Function :: atom(),
+ Arity :: arity(),
+ Docs :: docs_v1(),
+ Res :: unicode:chardata() | {error,function_missing}.
+render(Module, Function, #docs_v1{ docs = Docs } = D, Config)
+ when is_atom(Module), is_atom(Function), is_map(Config) ->
render_function(
- lists:filter(fun({{function, F, _},_Anno,_Sig,_Doc,_Meta}) ->
+ lists:filter(fun({{function, F, _},_Anno,_Sig,_Doc,_Meta}) ->
F =:= Function;
(_) ->
false
- end, Docs), D).
--spec render(Module :: module(), Function :: atom(), Arity :: arity(),
- Docs :: docs_v1()) -> unicode:chardata() | {error,function_missing}.
-render(_Module, Function, Arity, #docs_v1{ docs = Docs } = D) ->
+ end, Docs), D, Config);
+render(_Module, Function, Arity, #docs_v1{ } = D) ->
+ render(_Module, Function, Arity, D, #{}).
+
+-spec render(Module, Function, Arity, Docs, Config) -> Res when
+ Module :: module(),
+ Function :: atom(),
+ Arity :: arity(),
+ Docs :: docs_v1(),
+ Config :: config(),
+ Res :: unicode:chardata() | {error,function_missing}.
+render(Module, Function, Arity, #docs_v1{ docs = Docs } = D, Config)
+ when is_atom(Module), is_atom(Function), is_integer(Arity), is_map(Config) ->
render_function(
lists:filter(fun({{function, F, A},_Anno,_Sig,_Doc,_Meta}) ->
F =:= Function andalso A =:= Arity;
(_) ->
false
- end, Docs), D).
+ end, Docs), D, Config).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% API function for dealing with the type documentation
@@ -372,29 +419,56 @@ get_type_doc(Module, Type, Arity) ->
end, Docs),
[{F,A,S,get_local_doc(F, D),M} || {F,A,S,D,M} <- FnFunctions].
--spec render_type(Module :: module(), Docs :: docs_v1()) -> unicode:chardata().
+-spec render_type(Module, Docs) -> unicode:chardata() when
+ Module :: module(),
+ Docs :: docs_v1().
render_type(Module, D) ->
- render_signature_listing(Module, type, D).
-
--spec render_type(Module :: module(), Type :: atom(), Docs :: docs_v1()) ->
- unicode:chardata() | {error,type_missing}.
-render_type(_Module, Type, #docs_v1{ docs = Docs } = D) ->
+ render_type(Module, D, #{}).
+
+-spec render_type(Module, Docs, Config) -> unicode:chardata() when
+ Module :: module(),
+ Docs :: docs_v1(),
+ Config :: config();
+ (Module, Type, Docs) -> Res when
+ Module :: module(), Type :: atom(),
+ Docs :: docs_v1(),
+ Res :: unicode:chardata() | {error, type_missing}.
+render_type(Module, D = #docs_v1{}, Config) ->
+ render_signature_listing(Module, type, D, Config);
+render_type(Module, Type, D = #docs_v1{}) ->
+ render_type(Module, Type, D, #{}).
+
+-spec render_type(Module, Type, Docs, Config) -> Res when
+ Module :: module(), Type :: atom(),
+ Docs :: docs_v1(),
+ Config :: config(),
+ Res :: unicode:chardata() | {error, type_missing};
+ (Module, Type, Arity, Docs) -> Res when
+ Module :: module(), Type :: atom(), Arity :: arity(),
+ Docs :: docs_v1(),
+ Res :: unicode:chardata() | {error, type_missing}.
+render_type(_Module, Type, #docs_v1{ docs = Docs } = D, Config) ->
render_typecb_docs(
lists:filter(fun({{type, T, _},_Anno,_Sig,_Doc,_Meta}) ->
- T =:= Type;
- (_) ->
- false
- end, Docs), D).
-
--spec render_type(Module :: module(), Type :: atom(), Arity :: arity(),
- Docs :: docs_v1()) -> unicode:chardata() | {error,type_missing}.
-render_type(_Module, Type, Arity, #docs_v1{ docs = Docs } = D) ->
+ T =:= Type;
+ (_) ->
+ false
+ end, Docs), D, Config);
+render_type(_Module, Type, Arity, #docs_v1{ } = D) ->
+ render_type(_Module, Type, Arity, D, #{}).
+
+-spec render_type(Module, Type, Arity, Docs, Config) -> Res when
+ Module :: module(), Type :: atom(), Arity :: arity(),
+ Docs :: docs_v1(),
+ Config :: config(),
+ Res :: unicode:chardata() | {error, type_missing}.
+render_type(_Module, Type, Arity, #docs_v1{ docs = Docs } = D, Config) ->
render_typecb_docs(
lists:filter(fun({{type, T, A},_Anno,_Sig,_Doc,_Meta}) ->
T =:= Type andalso A =:= Arity;
(_) ->
false
- end, Docs), D).
+ end, Docs), D, Config).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% API function for dealing with the callback documentation
@@ -416,29 +490,56 @@ get_callback_doc(Module, Callback, Arity) ->
end, Docs),
[{F,A,S,get_local_doc(F, D),M} || {F,A,S,D,M} <- FnFunctions].
--spec render_callback(Module :: module(), Docs :: docs_v1()) -> unicode:chardata().
+-spec render_callback(Module, Docs) -> unicode:chardata() when
+ Module :: module(),
+ Docs :: docs_v1().
render_callback(Module, D) ->
- render_signature_listing(Module, callback, D).
-
--spec render_callback(Module :: module(), Callback :: atom(), Docs :: docs_v1()) ->
- unicode:chardata() | {error,callback_missing}.
-render_callback(_Module, Callback, #docs_v1{ docs = Docs } = D) ->
+ render_callback(Module, D, #{}).
+
+-spec render_callback(Module, Docs, Config) -> unicode:chardata() when
+ Module :: module(),
+ Docs :: docs_v1(),
+ Config :: config();
+ (Module, Callback, Docs) -> Res when
+ Module :: module(), Callback :: atom(),
+ Docs :: docs_v1(),
+ Res :: unicode:chardata() | {error, callback_missing}.
+render_callback(_Module, Callback, #docs_v1{ } = D) ->
+ render_callback(_Module, Callback, D, #{});
+render_callback(Module, D, Config) ->
+ render_signature_listing(Module, callback, D, Config).
+
+-spec render_callback(Module, Callback, Docs, Config) -> Res when
+ Module :: module(), Callback :: atom(),
+ Docs :: docs_v1(),
+ Config :: config(),
+ Res :: unicode:chardata() | {error, callback_missing};
+ (Module, Callback, Arity, Docs) -> Res when
+ Module :: module(), Callback :: atom(), Arity :: arity(),
+ Docs :: docs_v1(),
+ Res :: unicode:chardata() | {error, callback_missing}.
+render_callback(_Module, Callback, Arity, #docs_v1{ } = D) ->
+ render_callback(_Module, Callback, Arity, D, #{});
+render_callback(_Module, Callback, #docs_v1{ docs = Docs } = D, Config) ->
render_typecb_docs(
lists:filter(fun({{callback, T, _},_Anno,_Sig,_Doc,_Meta}) ->
- T =:= Callback;
- (_) ->
- false
- end, Docs), D).
-
--spec render_callback(Module :: module(), Callback :: atom(), Arity :: arity(),
- Docs :: docs_v1()) -> unicode:chardata() | {error,callback_missing}.
-render_callback(_Module, Callback, Arity, #docs_v1{ docs = Docs } = D) ->
+ T =:= Callback;
+ (_) ->
+ false
+ end, Docs), D, Config).
+
+-spec render_callback(Module, Callback, Arity, Docs, Config) -> Res when
+ Module :: module(), Callback :: atom(), Arity :: arity(),
+ Docs :: docs_v1(),
+ Config :: config(),
+ Res :: unicode:chardata() | {error, callback_missing}.
+render_callback(_Module, Callback, Arity, #docs_v1{ docs = Docs } = D, Config) ->
render_typecb_docs(
lists:filter(fun({{callback, T, A},_Anno,_Sig,_Doc,_Meta}) ->
T =:= Callback andalso A =:= Arity;
(_) ->
false
- end, Docs), D).
+ end, Docs), D, Config).
%% Get the docs in the correct locale if it exists.
get_local_doc(MissingMod, Docs) when is_atom(MissingMod) ->
@@ -459,9 +560,9 @@ get_local_doc(Missing, None) when None =:= none; None =:= #{} ->
[{p,[],[<<"There is no documentation for ">>,Missing]}].
%%% Functions for rendering reference documentation
-render_function([], _D) ->
+render_function([], _D, _Config) ->
{error,function_missing};
-render_function(FDocs, #docs_v1{ docs = Docs } = D) ->
+render_function(FDocs, #docs_v1{ docs = Docs } = D, Config) ->
Grouping =
lists:foldl(
fun({_Group,_Anno,_Sig,_Doc,#{ equiv := Group }} = Func,Acc) ->
@@ -478,13 +579,13 @@ render_function(FDocs, #docs_v1{ docs = Docs } = D) ->
Doc =/= #{}
end, Members) of
{value, {_,_,_,Doc,_Meta}} ->
- render_headers_and_docs(Signatures, get_local_doc({F,A},Doc), D);
+ render_headers_and_docs(Signatures, get_local_doc({F,A},Doc), D, Config);
false ->
case lists:keyfind(Group, 1, Docs) of
false ->
- render_headers_and_docs(Signatures, get_local_doc({F,A},none), D);
+ render_headers_and_docs(Signatures, get_local_doc({F,A},none), D, Config);
{_,_,_,Doc,_} ->
- render_headers_and_docs(Signatures, get_local_doc({F,A},Doc), D)
+ render_headers_and_docs(Signatures, get_local_doc({F,A},Doc), D, Config)
end
end
end, maps:to_list(Grouping)).
@@ -528,17 +629,19 @@ render_meta_(#{ deprecated := Depr } = M) ->
render_meta_(_) ->
[].
-render_headers_and_docs(Headers, DocContents, D) ->
+render_headers_and_docs(Headers, DocContents, D, Config) ->
+ render_headers_and_docs(Headers, DocContents, init_config(D, Config)).
+render_headers_and_docs(Headers, DocContents, #config{} = Config) ->
["\n",render_docs(
lists:flatmap(
fun(Header) ->
[{br,[],[]},Header]
- end,Headers), 0, D),
+ end,Headers), Config),
"\n",
- render_docs(DocContents,2,D)].
+ render_docs(DocContents, 2, Config)].
%%% Functions for rendering type/callback documentation
-render_signature_listing(Module, Type, #docs_v1{ docs = Docs } = D) ->
+render_signature_listing(Module, Type, #docs_v1{ docs = Docs } = D, Config) ->
Slogan = [{h2,[],[<<"\t",(atom_to_binary(Module))/binary>>]},{br,[],[]}],
case lists:filter(fun({{T, _, _},_Anno,_Sig,_Doc,_Meta}) ->
Type =:= T
@@ -546,7 +649,7 @@ render_signature_listing(Module, Type, #docs_v1{ docs = Docs } = D) ->
[] ->
render_docs(
Slogan ++ [<<"There are no ",(atom_to_binary(Type))/binary,"s "
- "in this module">>], D);
+ "in this module">>], D, Config);
Headers ->
Hdr = lists:flatmap(
fun(Header) ->
@@ -556,7 +659,7 @@ render_signature_listing(Module, Type, #docs_v1{ docs = Docs } = D) ->
Slogan ++
[{p,[],[<<"These ",(atom_to_binary(Type))/binary,"s "
"are documented in this module:">>]},
- {br,[],[]}, Hdr], D)
+ {br,[],[]}, Hdr], D, Config)
end.
render_typecb_docs([], _D) ->
@@ -564,27 +667,48 @@ render_typecb_docs([], _D) ->
render_typecb_docs(TypeCBs, #config{} = D) when is_list(TypeCBs) ->
[render_typecb_docs(TypeCB, D) || TypeCB <- TypeCBs];
render_typecb_docs({{_,F,A},_,_Sig,Docs,_Meta} = TypeCB, #config{} = D) ->
- render_headers_and_docs(render_signature(TypeCB), get_local_doc({F,A},Docs), D);
-render_typecb_docs(Docs, D) ->
- render_typecb_docs(Docs, #config{ docs = D }).
+ render_headers_and_docs(render_signature(TypeCB), get_local_doc({F,A},Docs), D).
+render_typecb_docs(Docs, D, Config) ->
+ render_typecb_docs(Docs, init_config(D, Config)).
%%% General rendering functions
-render_docs(DocContents, D) ->
- render_docs(DocContents, 0, D).
-render_docs(DocContents, Ind, D = #config{}) ->
+render_docs(DocContents, #config{} = Config) ->
+ render_docs(DocContents, 0, Config).
+render_docs(DocContents, D, Config) when is_map(Config) ->
+ render_docs(DocContents, 0, init_config(D, Config));
+render_docs(DocContents, Ind, D = #config{}) when is_integer(Ind) ->
init_ansi(D),
try
{Doc,_} = trimnl(render_docs(DocContents, [], 0, Ind, D)),
Doc
after
clean_ansi()
- end;
-render_docs(DocContents, Ind, D) ->
- render_docs(DocContents, Ind, #config{ docs = D }).
+ end.
+
+init_config(D, Config) ->
+ DefaultOpts = io:getopts(),
+ DefaultEncoding = proplists:get_value(encoding, DefaultOpts, latin1),
+ Columns =
+ case maps:find(columns, Config) of
+ error ->
+ case io:columns() of
+ {ok, C} ->
+ C;
+ _ ->
+ 80
+ end;
+ {ok, C} ->
+ C
+ end,
+ #config{ docs = D,
+ encoding = maps:get(encoding, Config, DefaultEncoding),
+ ansi = maps:get(ansi, Config, undefined),
+ columns = Columns
+ }.
render_docs(Elems,State,Pos,Ind,D) when is_list(Elems) ->
lists:mapfoldl(fun(Elem,P) ->
-% io:format("Elem: ~p (~p) (~p,~p)~n",[Elem,State,P,Ind]),
+%%% io:format("Elem: ~p (~p) (~p,~p)~n",[Elem,State,P,Ind]),
render_docs(Elem,State,P,Ind,D)
end,Pos,Elems);
render_docs(Elem,State,Pos,Ind,D) ->
@@ -625,6 +749,10 @@ render_element({h2,_,Content},State,0 = Pos,_Ind,D) ->
render_element({h3,_,Content},State,Pos,_Ind,D) when Pos =< 2 ->
trimnlnl(render_element({code,[],Content}, State, Pos, 2, D));
+render_element({pre,_Attr,_Content} = E,State,Pos,Ind,D) when Pos > Ind ->
+ %% We pad `pre` with two newlines if the previous section did not indent the region.
+ {Docs,NewPos} = render_element(E,State,0,Ind,D),
+ {["\n\n",Docs],NewPos};
render_element({Elem,_Attr,_Content} = E,State,Pos,Ind,D) when Pos > Ind, ?IS_BLOCK(Elem) ->
{Docs,NewPos} = render_element(E,State,0,Ind,D),
{["\n",Docs],NewPos};
@@ -635,9 +763,7 @@ render_element({Tag,_,Content},State,Pos,Ind,D) when Tag =:= p; Tag =:= 'div' ->
trimnlnl(render_docs(Content, [Tag|State], Pos, Ind, D));
render_element(Elem,State,Pos,Ind,D) when Pos < Ind ->
-% io:format("Pad: ~p~n",[Ind - Pos]),
{Docs,NewPos} = render_element(Elem,State,Ind,Ind,D),
-
{[pad(Ind - Pos), Docs],NewPos};
render_element({code,_,Content},[pre|_] = State,Pos,Ind,D) ->
@@ -697,7 +823,7 @@ render_element({ol,[],Content},State,Pos,Ind,D) ->
%% For now ul and ol does the same thing
render_docs(Content, [l|State], Pos, Ind,D);
render_element({li,[],Content},[l | _] = State, Pos, Ind,D) ->
- Bullet = get_bullet(State, proplists:get_value(encoding, D#config.io_opts)),
+ Bullet = get_bullet(State, D#config.encoding),
BulletLen = string:length(Bullet),
{Docs, _NewPos} = render_docs(Content, [li | State], Pos + BulletLen,Ind + BulletLen, D),
trimnlnl([Bullet,Docs]);
@@ -711,10 +837,10 @@ render_element({dt,_,Content},[dl | _] = State,Pos,Ind,D) ->
render_element({dd,_,Content},[dl | _] = State,Pos,Ind,D) ->
trimnlnl(render_docs(Content, [li | State], Pos, Ind + 2, D));
-render_element(B, State, Pos, Ind,#config{ io_columns = Cols }) when is_binary(B) ->
+render_element(B, State, Pos, Ind,#config{ columns = Cols }) when is_binary(B) ->
case lists:member(pre,State) of
true ->
- Pre = string:replace(B,"\n",["\n",pad(Ind)],all),
+ Pre = string:replace(B,"\n",[nlpad(Ind)],all),
{Pre, Pos + lastline(Pre)};
_ ->
render_words(split_to_words(B),State,Pos,Ind,[[]],Cols)
@@ -742,8 +868,8 @@ render_words([Word|T],State,Pos,Ind,Acc,Cols) when is_binary(Word) ->
if
NewPos > (Cols - 10 - Ind), Word =/= <<>>, not IsPunct ->
%% Word does not fit, time to add a newline and also pad to Indent level
- render_words(T,State,WordLength+Ind+1,Ind,[[[pad(Ind), Word]]|Acc],Cols);
- true ->
+ render_words(T,State,WordLength+Ind+1,Ind,[[[nlpad(Ind), Word]]|Acc],Cols);
+ true ->
%% Word does fit on line
[Line | LineAcc] = Acc,
%% Add + 1 to length for space
@@ -751,11 +877,10 @@ render_words([Word|T],State,Pos,Ind,Acc,Cols) when is_binary(Word) ->
render_words(T,State,NewPosSpc,Ind,[[Word|Line]|LineAcc],Cols)
end;
render_words([],_State,Pos,_Ind,Acc,_Cols) ->
- Lines = lists:join(
- $\n,lists:map(fun(RevLine) ->
- Line = lists:reverse(RevLine),
- lists:join($ ,Line)
- end,lists:reverse(Acc))),
+ Lines = lists:map(fun(RevLine) ->
+ Line = lists:reverse(RevLine),
+ lists:join($ ,Line)
+ end,lists:reverse(Acc)),
{iolist_to_binary(Lines), Pos}.
render_type_signature(Name, #config{ docs = #docs_v1{ metadata = #{ types := AllTypes }}}) ->
@@ -766,14 +891,21 @@ render_type_signature(Name, #config{ docs = #docs_v1{ metadata = #{ types := All
[erl_pp:attribute(maps:get(Type, AllTypes)) || Type <- Types]
end.
-%% Pad N spaces, disabling any ansi formatting while doing so
+%% Pad N spaces (and possibly pre-prend newline), disabling any ansi formatting while doing so.
pad(N) ->
+ pad(N,"").
+nlpad(N) ->
+ %% It is important that we disable the ansi code before the new-line as otherwise the
+ %% ansi decoration may be enabled when c:paged_output tries to ask if more content
+ %% should be displayed.
+ pad(N,"\n").
+pad(N, Extra) ->
Pad = lists:duplicate(N," "),
case ansi() of
undefined ->
- Pad;
+ [Extra, Pad];
Ansi ->
- ["\033[0m",Pad,Ansi]
+ ["\033[0m",Extra,Pad,Ansi]
end.
get_bullet(_State,latin1) ->
@@ -789,7 +921,7 @@ get_bullet(State,unicode) ->
<<" ◼ "/utf8>>,<<" ◻ "/utf8>>])
end.
-% Look for the length of the last line of a string
+%% Look for the length of the last line of a string
lastline(Str) ->
LastStr = case string:find(Str,"\n",trailing) of
nomatch ->
@@ -819,7 +951,7 @@ nl(Chars) ->
%% We keep the current ansi state in the pdict so that we know
%% what to disable and enable when doing padding
-init_ansi(#config{ io_opts = Opts }) ->
+init_ansi(#config{ ansi = undefined, io_opts = Opts }) ->
%% We use this as our heuristic to see if we should print ansi or not
case {application:get_env(kernel, shell_docs_ansi),
proplists:is_defined(echo, Opts) andalso
@@ -835,7 +967,13 @@ init_ansi(#config{ io_opts = Opts }) ->
put(ansi, []);
{_, false,_} ->
put(ansi, noansi)
- end.
+ end;
+init_ansi(#config{ ansi = true }) ->
+ put(ansi, []);
+init_ansi(#config{ ansi = false }) ->
+ put(ansi, noansi).
+
+
clean_ansi() ->
case get(ansi) of
diff --git a/lib/stdlib/src/stdlib.appup.src b/lib/stdlib/src/stdlib.appup.src
index f66ff560b3..2f44c251e0 100644
--- a/lib/stdlib/src/stdlib.appup.src
+++ b/lib/stdlib/src/stdlib.appup.src
@@ -40,6 +40,7 @@
{<<"^3\\.13$">>,[restart_new_emulator]},
{<<"^3\\.13\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^3\\.13\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.13\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^3\\.5$">>,[restart_new_emulator]},
{<<"^3\\.5\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^3\\.5\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
@@ -68,6 +69,7 @@
{<<"^3\\.13$">>,[restart_new_emulator]},
{<<"^3\\.13\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^3\\.13\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.13\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^3\\.5$">>,[restart_new_emulator]},
{<<"^3\\.5\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^3\\.5\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
diff --git a/lib/stdlib/src/uri_string.erl b/lib/stdlib/src/uri_string.erl
index 0b84a8a91d..3060f2bfaa 100644
--- a/lib/stdlib/src/uri_string.erl
+++ b/lib/stdlib/src/uri_string.erl
@@ -226,10 +226,21 @@
%%-------------------------------------------------------------------------
%% External API
%%-------------------------------------------------------------------------
--export([compose_query/1, compose_query/2,
- dissect_query/1, normalize/1, normalize/2, parse/1,
- recompose/1, resolve/2, resolve/3, transcode/2]).
--export_type([error/0, uri_map/0, uri_string/0]).
+-export([allowed_characters/0,
+ compose_query/1,
+ compose_query/2,
+ dissect_query/1,
+ normalize/1,
+ normalize/2,
+ percent_decode/1,
+ parse/1,
+ recompose/1,
+ resolve/2,
+ resolve/3,
+ transcode/2]).
+-export_type([error/0,
+ uri_map/0,
+ uri_string/0]).
%%-------------------------------------------------------------------------
@@ -286,7 +297,7 @@
port => non_neg_integer() | undefined,
query => unicode:chardata(),
scheme => unicode:chardata(),
- userinfo => unicode:chardata()} | #{}.
+ userinfo => unicode:chardata()}.
%%-------------------------------------------------------------------------
@@ -453,6 +464,61 @@ transcode(URIString, Options) when is_list(URIString) ->
%%-------------------------------------------------------------------------
+%% Misc
+%%-------------------------------------------------------------------------
+-spec allowed_characters() -> [{atom(), list()}].
+allowed_characters() ->
+ Input = lists:seq(0,127),
+ Scheme = lists:filter(fun is_scheme/1, Input),
+ UserInfo = lists:filter(fun is_userinfo/1, Input),
+ Host = lists:filter(fun is_host/1, Input),
+ IPv4 = lists:filter(fun is_ipv4/1, Input),
+ IPv6 = lists:filter(fun is_ipv6/1, Input),
+ RegName = lists:filter(fun is_reg_name/1, Input),
+ Path = lists:filter(fun is_path/1, Input),
+ Query = lists:filter(fun is_query/1, Input),
+ Fragment = lists:filter(fun is_fragment/1, Input),
+ Reserved = lists:filter(fun is_reserved/1, Input),
+ Unreserved = lists:filter(fun is_unreserved/1, Input),
+ [{scheme, Scheme},
+ {userinfo, UserInfo},
+ {host, Host},
+ {ipv4, IPv4},
+ {ipv6, IPv6},
+ {regname,RegName},
+ {path,Path},
+ {query, Query},
+ {fragment,Fragment},
+ {reserved, Reserved},
+ {unreserved, Unreserved}].
+
+-spec percent_decode(URI) -> Result when
+ URI :: uri_string() | uri_map(),
+ Result :: uri_string() |
+ uri_map() |
+ {error, {invalid, {atom(), {term(), term()}}}}.
+percent_decode(URIMap) when is_map(URIMap)->
+ Fun = fun (K,V) when K =:= userinfo; K =:= host; K =:= path;
+ K =:= query; K =:= fragment ->
+ case raw_decode(V) of
+ {error, Reason, Input} ->
+ throw({error, {invalid, {K, {Reason, Input}}}});
+ Else ->
+ Else
+ end;
+ %% Handle port and scheme
+ (_,V) ->
+ V
+ end,
+ try maps:map(Fun, URIMap)
+ catch throw:Return ->
+ Return
+ end;
+percent_decode(URI) when is_list(URI) orelse
+ is_binary(URI) ->
+ raw_decode(URI).
+
+%%-------------------------------------------------------------------------
%% Functions for working with the query part of a URI as a list
%% of key/value pairs.
%% HTML 5.2 - 4.10.21.6 URL-encoded form data - WHATWG URL (10 Jan 2018) - UTF-8
@@ -1421,8 +1487,15 @@ decode(<<$%,C0,C1,Cs/binary>>, Acc) ->
case is_hex_digit(C0) andalso is_hex_digit(C1) of
true ->
B = ?HEX2DEC(C0)*16+?HEX2DEC(C1),
- case is_reserved(B) of
- true ->
+ %% [2.4] When a URI is dereferenced, the components and subcomponents
+ %% significant to the scheme-specific dereferencing process (if any)
+ %% must be parsed and separated before the percent-encoded octets within
+ %% those components can be safely decoded, as otherwise the data may be
+ %% mistaken for component delimiters. The only exception is for
+ %% percent-encoded octets corresponding to characters in the unreserved
+ %% set, which can be decoded at any time.
+ case is_unreserved(B) of
+ false ->
%% [2.2] Characters in the reserved set are protected from
%% normalization.
%% [2.1] For consistency, URI producers and normalizers should
@@ -1431,7 +1504,7 @@ decode(<<$%,C0,C1,Cs/binary>>, Acc) ->
H0 = hex_to_upper(C0),
H1 = hex_to_upper(C1),
decode(Cs, <<Acc/binary,$%,H0,H1>>);
- false ->
+ true ->
decode(Cs, <<Acc/binary, B>>)
end;
false -> throw({error,invalid_percent_encoding,<<$%,C0,C1>>})
@@ -1441,6 +1514,32 @@ decode(<<C,Cs/binary>>, Acc) ->
decode(<<>>, Acc) ->
check_utf8(Acc).
+-spec raw_decode(list()|binary()) -> list() | binary() | error().
+raw_decode(Cs) ->
+ raw_decode(Cs, <<>>).
+%%
+raw_decode(L, Acc) when is_list(L) ->
+ try
+ B0 = unicode:characters_to_binary(L),
+ B1 = raw_decode(B0, Acc),
+ unicode:characters_to_list(B1)
+ catch
+ throw:{error, Atom, RestData} ->
+ {error, Atom, RestData}
+ end;
+raw_decode(<<$%,C0,C1,Cs/binary>>, Acc) ->
+ case is_hex_digit(C0) andalso is_hex_digit(C1) of
+ true ->
+ B = ?HEX2DEC(C0)*16+?HEX2DEC(C1),
+ raw_decode(Cs, <<Acc/binary, B>>);
+ false ->
+ throw({error,invalid_percent_encoding,<<$%,C0,C1>>})
+ end;
+raw_decode(<<C,Cs/binary>>, Acc) ->
+ raw_decode(Cs, <<Acc/binary, C>>);
+raw_decode(<<>>, Acc) ->
+ check_utf8(Acc).
+
%% Returns Cs if it is utf8 encoded.
check_utf8(Cs) ->
case unicode:characters_to_list(Cs) of
@@ -1681,7 +1780,8 @@ update_path(#{path := Path}, empty) ->
encode_path(Path);
update_path(#{host := _, path := Path0}, URI) ->
%% When host is present in a URI the path must begin with "/" or be empty.
- Path = make_path_absolute(Path0),
+ Path1 = maybe_flatten_list(Path0),
+ Path = make_path_absolute(Path1),
concat(URI,encode_path(Path));
update_path(#{path := Path}, URI) ->
concat(URI,encode_path(Path));
@@ -1785,6 +1885,11 @@ make_path_absolute(Path) when is_binary(Path) ->
make_path_absolute(Path) when is_list(Path) ->
concat("/", Path).
+maybe_flatten_list(Path) when is_binary(Path) ->
+ Path;
+maybe_flatten_list(Path) ->
+ unicode:characters_to_list(Path).
+
%%-------------------------------------------------------------------------
%% Helper functions for resolve
%%-------------------------------------------------------------------------
@@ -1897,7 +2002,7 @@ transcode_pct([], Acc, B, InEncoding, OutEncoding) ->
OutBinary = convert_to_binary(B, InEncoding, OutEncoding),
PctEncUtf8 = percent_encode_segment(OutBinary),
Out = convert_to_list(PctEncUtf8, utf8),
- lists:reverse(Acc) ++ Out.
+ lists:reverse(Acc, Out).
%% Convert to binary
@@ -1932,7 +2037,7 @@ flatten_list(L, InEnc) ->
%%
flatten_list([H|T], InEnc, Acc) when is_binary(H) ->
L = convert_to_list(H, InEnc),
- flatten_list(T, InEnc, lists:reverse(L) ++ Acc);
+ flatten_list(T, InEnc, lists:reverse(L, Acc));
flatten_list([H|T], InEnc, Acc) when is_list(H) ->
flatten_list(H ++ T, InEnc, Acc);
flatten_list([H|T], InEnc, Acc) ->
@@ -1952,7 +2057,7 @@ percent_encode_segment(Segment) ->
%%-------------------------------------------------------------------------
%% Returns separator to be used between key-value pairs
-get_separator(L) when length(L) =:= 0 ->
+get_separator([]) ->
<<>>;
get_separator(_L) ->
<<"&">>.
diff --git a/lib/stdlib/test/property_test/uri_string_recompose.erl b/lib/stdlib/test/property_test/uri_string_recompose.erl
index 39fadf23c2..3c0dae0f8b 100644
--- a/lib/stdlib/test/property_test/uri_string_recompose.erl
+++ b/lib/stdlib/test/property_test/uri_string_recompose.erl
@@ -85,9 +85,12 @@ prop_recompose() ->
prop_normalize() ->
?FORALL(Map, map(),
- uri_string:normalize(Map, [return_map]) =:=
- uri_string:normalize(uri_string:parse(uri_string:recompose(Map)),
- [return_map])).
+ uri_string:percent_decode(
+ uri_string:normalize(Map, [return_map])) =:=
+ uri_string:percent_decode(
+ uri_string:normalize(
+ uri_string:parse(uri_string:recompose(Map)),
+ [return_map]))).
%% Stats
prop_map_key_length_collect() ->
diff --git a/lib/stdlib/test/shell_docs_SUITE.erl b/lib/stdlib/test/shell_docs_SUITE.erl
index 11282db3e7..222aec5e0b 100644
--- a/lib/stdlib/test/shell_docs_SUITE.erl
+++ b/lib/stdlib/test/shell_docs_SUITE.erl
@@ -60,35 +60,44 @@ end_per_group(_GroupName, Config) ->
render(_Config) ->
docsmap(
fun(Mod, #docs_v1{ docs = Docs } = D) ->
- try
- shell_docs:render(Mod, D),
- shell_docs:render_type(Mod, D),
- shell_docs:render_callback(Mod, D),
- [try
- shell_docs:render(Mod, F, A, D)
- catch _E:R:ST ->
- io:format("Failed to render ~p:~p/~p~n~p:~p~n~p~n",
- [Mod,F,A,R,ST,shell_docs:get_doc(Mod,F,A)]),
- erlang:raise(error,R,ST)
- end || {F,A} <- Mod:module_info(exports)],
- [try
- shell_docs:render_type(Mod, T, A, D)
- catch _E:R:ST ->
- io:format("Failed to render type ~p:~p/~p~n~p:~p~n~p~n",
- [Mod,T,A,R,ST,shell_docs:get_type_doc(Mod,T,A)]),
- erlang:raise(error,R,ST)
- end || {{type,T,A},_,_,_,_} <- Docs],
- [try
- shell_docs:render_callback(Mod, T, A, D)
- catch _E:R:ST ->
- io:format("Failed to render callback ~p:~p/~p~n~p:~p~n~p~n",
- [Mod,T,A,R,ST,shell_docs:get_callback_doc(Mod,T,A)]),
- erlang:raise(error,R,ST)
- end || {{callback,T,A},_,_,_,_} <- Docs]
- catch throw:R:ST ->
- io:format("Failed to render ~p~n~p:~p~n",[Mod,R,ST]),
- exit(R)
- end
+ lists:foreach(
+ fun(Config) ->
+ try
+ shell_docs:render(Mod, D, Config),
+ shell_docs:render_type(Mod, D, Config),
+ shell_docs:render_callback(Mod, D, Config),
+ [try
+ shell_docs:render(Mod, F, A, D, Config)
+ catch _E:R:ST ->
+ io:format("Failed to render ~p:~p/~p~n~p:~p~n~p~n",
+ [Mod,F,A,R,ST,shell_docs:get_doc(Mod,F,A)]),
+ erlang:raise(error,R,ST)
+ end || {F,A} <- Mod:module_info(exports)],
+ [try
+ shell_docs:render_type(Mod, T, A, D, Config)
+ catch _E:R:ST ->
+ io:format("Failed to render type ~p:~p/~p~n~p:~p~n~p~n",
+ [Mod,T,A,R,ST,shell_docs:get_type_doc(Mod,T,A)]),
+ erlang:raise(error,R,ST)
+ end || {{type,T,A},_,_,_,_} <- Docs],
+ [try
+ shell_docs:render_callback(Mod, T, A, D, Config)
+ catch _E:R:ST ->
+ io:format("Failed to render callback ~p:~p/~p~n~p:~p~n~p~n",
+ [Mod,T,A,R,ST,shell_docs:get_callback_doc(Mod,T,A)]),
+ erlang:raise(error,R,ST)
+ end || {{callback,T,A},_,_,_,_} <- Docs]
+ catch throw:R:ST ->
+ io:format("Failed to render ~p~n~p:~p~n",[Mod,R,ST]),
+ exit(R)
+ end
+ end, [#{},
+ #{ ansi => false },
+ #{ ansi => true },
+ #{ columns => 5 },
+ #{ columns => 150 },
+ #{ encoding => unicode},
+ #{ encoding => latin1}])
end),
ok.
diff --git a/lib/stdlib/test/uri_string_SUITE.erl b/lib/stdlib/test/uri_string_SUITE.erl
index 51f6aac7ad..96e4a9d41b 100644
--- a/lib/stdlib/test/uri_string_SUITE.erl
+++ b/lib/stdlib/test/uri_string_SUITE.erl
@@ -52,7 +52,8 @@
dissect_query/1, dissect_query_negative/1,
interop_query_latin1/1, interop_query_utf8/1,
regression_parse/1, regression_recompose/1, regression_normalize/1,
- recompose_host_relative_path/1
+ recompose_host_relative_path/1,
+ recompose_host_absolute_path/1
]).
@@ -146,7 +147,8 @@ all() ->
regression_parse,
regression_recompose,
regression_normalize,
- recompose_host_relative_path
+ recompose_host_relative_path,
+ recompose_host_absolute_path
].
groups() ->
@@ -1049,14 +1051,20 @@ normalize_map(_Config) ->
normalize_return_map(_Config) ->
#{scheme := "http",path := "/a/g",host := "localhost-örebro"} =
- uri_string:normalize("http://localhos%74-%c3%b6rebro:80/a/b/c/./../../g",
- [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize(
+ "http://localhos%74-%c3%b6rebro:80/a/b/c/./../../g",
+ [return_map])),
#{scheme := <<"http">>,path := <<"/a/g">>, host := <<"localhost-örebro"/utf8>>} =
- uri_string:normalize(<<"http://localhos%74-%c3%b6rebro:80/a/b/c/./../../g">>,
- [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize(
+ <<"http://localhos%74-%c3%b6rebro:80/a/b/c/./../../g">>,
+ [return_map])),
#{scheme := <<"https">>,path := <<"/">>, host := <<"localhost">>} =
- uri_string:normalize(#{scheme => <<"https">>,port => 443,path => <<>>,
- host => <<"localhost">>}, [return_map]).
+ uri_string:percent_decode(
+ uri_string:normalize(
+ #{scheme => <<"https">>,port => 443,path => <<>>,
+ host => <<"localhost">>}, [return_map])).
normalize_negative(_Config) ->
{error,invalid_uri,":"} =
@@ -1067,64 +1075,103 @@ normalize_negative(_Config) ->
uri_string:normalize("http://[192.168.0.1]", [return_map]),
{error,invalid_uri,":"} =
uri_string:normalize(<<"http://[192.168.0.1]">>, [return_map]),
- {error,invalid_utf8,<<0,0,0,246>>} = uri_string:normalize("//%00%00%00%F6").
+ {error,invalid_utf8,<<47,47,0,0,0,246>>} =
+ uri_string:percent_decode(uri_string:normalize("//%00%00%00%F6")).
normalize_binary_pct_encoded_userinfo(_Config) ->
#{scheme := <<"user">>, path := <<"合@気道"/utf8>>} =
- uri_string:normalize(<<"user:%E5%90%88@%E6%B0%97%E9%81%93">>, [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize(
+ <<"user:%E5%90%88@%E6%B0%97%E9%81%93">>, [return_map])),
#{path := <<"合気道@"/utf8>>} =
- uri_string:normalize(<<"%E5%90%88%E6%B0%97%E9%81%93@">>, [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize(
+ <<"%E5%90%88%E6%B0%97%E9%81%93@">>, [return_map])),
#{path := <<"/合気道@"/utf8>>} =
- uri_string:normalize(<<"/%E5%90%88%E6%B0%97%E9%81%93@">>, [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize(
+ <<"/%E5%90%88%E6%B0%97%E9%81%93@">>, [return_map])),
#{path := <<"合@気道"/utf8>>} =
- uri_string:normalize(<<"%E5%90%88@%E6%B0%97%E9%81%93">>, [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize(
+ <<"%E5%90%88@%E6%B0%97%E9%81%93">>, [return_map])),
#{userinfo := <<"合"/utf8>>, host := <<"気道"/utf8>>} =
- uri_string:normalize(<<"//%E5%90%88@%E6%B0%97%E9%81%93">>, [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize(
+ <<"//%E5%90%88@%E6%B0%97%E9%81%93">>, [return_map])),
#{userinfo := <<"合:気"/utf8>>, host := <<"道"/utf8>>} =
- uri_string:normalize(<<"//%E5%90%88:%E6%B0%97@%E9%81%93">>, [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize(
+ <<"//%E5%90%88:%E6%B0%97@%E9%81%93">>, [return_map])),
#{scheme := <<"foo">>, path := <<"/合気道@"/utf8>>} =
- uri_string:normalize(<<"foo:/%E5%90%88%E6%B0%97%E9%81%93@">>, [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize(
+ <<"foo:/%E5%90%88%E6%B0%97%E9%81%93@">>, [return_map])),
#{scheme := <<"foo">>, userinfo := <<"合"/utf8>>, host := <<"気道"/utf8>>} =
- uri_string:normalize(<<"foo://%E5%90%88@%E6%B0%97%E9%81%93">>, [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize(
+ <<"foo://%E5%90%88@%E6%B0%97%E9%81%93">>, [return_map])),
#{scheme := <<"foo">>, userinfo := <<"合:気"/utf8>>, host := <<"道"/utf8>>} =
- uri_string:normalize(<<"foo://%E5%90%88:%E6%B0%97@%E9%81%93">>, [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize(
+ <<"foo://%E5%90%88:%E6%B0%97@%E9%81%93">>, [return_map])),
{error,invalid_uri,"@"} =
- uri_string:normalize(<<"//%E5%90%88@%E6%B0%97%E9%81%93@">>, [return_map]),
+ uri_string:normalize(
+ <<"//%E5%90%88@%E6%B0%97%E9%81%93@">>, [return_map]),
{error,invalid_uri,":"} =
- uri_string:normalize(<<"foo://%E5%90%88@%E6%B0%97%E9%81%93@">>, [return_map]).
+ uri_string:normalize(
+ <<"foo://%E5%90%88@%E6%B0%97%E9%81%93@">>, [return_map]).
normalize_binary_pct_encoded_query(_Config) ->
#{scheme := <<"foo">>, host := <<"example.com">>, path := <<"/">>,
query := <<"name=合気道"/utf8>>} =
- uri_string:normalize(<<"foo://example.com/?name=%E5%90%88%E6%B0%97%E9%81%93">>, [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize(
+ <<"foo://example.com/?name=%E5%90%88%E6%B0%97%E9%81%93">>,
+ [return_map])),
#{host := <<"example.com">>, path := <<"/">>, query := <<"name=合気道"/utf8>>} =
- uri_string:normalize(<<"//example.com/?name=%E5%90%88%E6%B0%97%E9%81%93">>, [return_map]).
+ uri_string:percent_decode(
+ uri_string:normalize(
+ <<"//example.com/?name=%E5%90%88%E6%B0%97%E9%81%93">>, [return_map])).
normalize_binary_pct_encoded_fragment(_Config) ->
#{scheme := <<"foo">>, host := <<"example.com">>, fragment := <<"合気道"/utf8>>} =
- uri_string:normalize(<<"foo://example.com#%E5%90%88%E6%B0%97%E9%81%93">>, [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize(
+ <<"foo://example.com#%E5%90%88%E6%B0%97%E9%81%93">>, [return_map])),
#{host := <<"example.com">>, path := <<"/">>, fragment := <<"合気道"/utf8>>} =
- uri_string:normalize(<<"//example.com/#%E5%90%88%E6%B0%97%E9%81%93">>, [return_map]).
+ uri_string:percent_decode(
+ uri_string:normalize(
+ <<"//example.com/#%E5%90%88%E6%B0%97%E9%81%93">>, [return_map])).
normalize_pct_encoded_userinfo(_Config) ->
#{scheme := "user", path := "合@気道"} =
- uri_string:normalize("user:%E5%90%88@%E6%B0%97%E9%81%93", [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize("user:%E5%90%88@%E6%B0%97%E9%81%93", [return_map])),
#{path := "合気道@"} =
- uri_string:normalize("%E5%90%88%E6%B0%97%E9%81%93@", [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize("%E5%90%88%E6%B0%97%E9%81%93@", [return_map])),
#{path := "/合気道@"} =
- uri_string:normalize("/%E5%90%88%E6%B0%97%E9%81%93@", [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize("/%E5%90%88%E6%B0%97%E9%81%93@", [return_map])),
#{path := "合@気道"} =
- uri_string:normalize("%E5%90%88@%E6%B0%97%E9%81%93", [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize("%E5%90%88@%E6%B0%97%E9%81%93", [return_map])),
#{userinfo := "合", host := "気道"} =
- uri_string:normalize("//%E5%90%88@%E6%B0%97%E9%81%93", [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize("//%E5%90%88@%E6%B0%97%E9%81%93", [return_map])),
#{userinfo := "合:気", host := "道"} =
- uri_string:normalize("//%E5%90%88:%E6%B0%97@%E9%81%93", [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize("//%E5%90%88:%E6%B0%97@%E9%81%93", [return_map])),
#{scheme := "foo", path := "/合気道@"} =
- uri_string:normalize("foo:/%E5%90%88%E6%B0%97%E9%81%93@", [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize("foo:/%E5%90%88%E6%B0%97%E9%81%93@", [return_map])),
#{scheme := "foo", userinfo := "合", host := "気道"} =
- uri_string:normalize("foo://%E5%90%88@%E6%B0%97%E9%81%93", [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize("foo://%E5%90%88@%E6%B0%97%E9%81%93", [return_map])),
#{scheme := "foo", userinfo := "合:気", host := "道"} =
- uri_string:normalize("foo://%E5%90%88:%E6%B0%97@%E9%81%93", [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize("foo://%E5%90%88:%E6%B0%97@%E9%81%93", [return_map])),
{error,invalid_uri,"@"} =
uri_string:normalize("//%E5%90%88@%E6%B0%97%E9%81%93@", [return_map]),
{error,invalid_uri,":"} =
@@ -1133,25 +1180,37 @@ normalize_pct_encoded_userinfo(_Config) ->
normalize_pct_encoded_query(_Config) ->
#{scheme := "foo", host := "example.com", path := "/",
query := "name=合気道"} =
- uri_string:normalize("foo://example.com/?name=%E5%90%88%E6%B0%97%E9%81%93", [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize(
+ "foo://example.com/?name=%E5%90%88%E6%B0%97%E9%81%93", [return_map])),
#{host := "example.com", path := "/", query := "name=合気道"} =
- uri_string:normalize("//example.com/?name=%E5%90%88%E6%B0%97%E9%81%93", [return_map]).
+ uri_string:percent_decode(
+ uri_string:normalize(
+ "//example.com/?name=%E5%90%88%E6%B0%97%E9%81%93", [return_map])).
normalize_pct_encoded_fragment(_Config) ->
#{scheme := "foo", host := "example.com", fragment := "合気道"} =
- uri_string:normalize("foo://example.com#%E5%90%88%E6%B0%97%E9%81%93", [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize(
+ "foo://example.com#%E5%90%88%E6%B0%97%E9%81%93", [return_map])),
#{host := "example.com", path := "/", fragment := "合気道"} =
- uri_string:normalize("//example.com/#%E5%90%88%E6%B0%97%E9%81%93", [return_map]).
+ uri_string:percent_decode(
+ uri_string:normalize(
+ "//example.com/#%E5%90%88%E6%B0%97%E9%81%93", [return_map])).
normalize_pct_encoded_negative(_Config) ->
- {error,invalid_utf8,<<0,0,0,246>>} =
- uri_string:normalize(#{host => "%00%00%00%F6",path => []}, [return_map]),
- {error,invalid_utf8,<<0,0,0,246>>} =
- uri_string:normalize(#{host => "%00%00%00%F6",path => []}, []),
- {error,invalid_utf8,<<0,0,0,246>>} =
- uri_string:normalize("//%00%00%00%F6", [return_map]),
- {error,invalid_utf8,<<0,0,0,246>>} =
- uri_string:normalize("//%00%00%00%F6", []).
+ {error,{invalid,{host,{invalid_utf8,<<0,0,0,246>>}}}} =
+ uri_string:percent_decode(
+ uri_string:normalize(#{host => "%00%00%00%F6",path => []}, [return_map])),
+ {error,invalid_utf8,<<47,47,0,0,0,246>>} =
+ uri_string:percent_decode(
+ uri_string:normalize(#{host => "%00%00%00%F6",path => []}, [])),
+ {error,{invalid,{host,{invalid_utf8,<<0,0,0,246>>}}}} =
+ uri_string:percent_decode(
+ uri_string:normalize("//%00%00%00%F6", [return_map])),
+ {error,invalid_utf8,<<47,47,0,0,0,246>>} =
+ uri_string:percent_decode(
+ uri_string:normalize("//%00%00%00%F6", [])).
interop_query_utf8(_Config) ->
Q = uri_string:compose_query([{"foo bar","1"}, {"合", "2"}]),
@@ -1216,8 +1275,8 @@ regression_normalize(_Config) ->
"foo://%C3%B6" =
uri_string:normalize("FOo://%C3%B6"),
#{host := "ö",path := [],scheme := "foo"} =
- uri_string:normalize("FOo://%C3%B6", [return_map]),
-
+ uri_string:percent_decode(
+ uri_string:normalize("FOo://%C3%B6", [return_map])),
"foo://bar" =
uri_string:normalize(#{host => "Bar",path => [],scheme => "FOo"}),
@@ -1242,7 +1301,9 @@ regression_normalize(_Config) ->
"foo://%C3%B6" =
uri_string:normalize(#{host => "%C3%B6",path => [],scheme => "FOo"}),
#{host := "ö",path := [],scheme := "foo"} =
- uri_string:normalize(#{host => "%C3%B6",path => [],scheme => "FOo"}, [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize(#{host => "%C3%B6",path => [],scheme => "FOo"},
+ [return_map])),
"foo://%C3%B6" =
uri_string:normalize(#{host => "ö",path => [],scheme => "FOo"}),
@@ -1256,3 +1317,18 @@ recompose_host_relative_path(_Config) ->
uri_string:recompose(#{host => <<"example.com">>, path => <<"foo">>}),
ok.
+recompose_host_absolute_path(_Config) ->
+ "//example.com/foo" =
+ uri_string:recompose(#{host => "example.com",
+ path => ["/", "foo"]}),
+ "//example.com/foo" =
+ uri_string:recompose(#{host => <<"example.com">>,
+ path => [<<"/">>,<<"foo">>]}),
+ "//example.com/foo" =
+ uri_string:recompose(#{host => "example.com",
+ path => ["/f", "oo"]}),
+ "//example.com/foo" =
+ uri_string:recompose(#{host => <<"example.com">>,
+ path => [<<"/f">>,<<"oo">>]}),
+ ok.
+
diff --git a/lib/stdlib/vsn.mk b/lib/stdlib/vsn.mk
index a03fc23e00..5f8e568387 100644
--- a/lib/stdlib/vsn.mk
+++ b/lib/stdlib/vsn.mk
@@ -1 +1 @@
-STDLIB_VSN = 3.13.2
+STDLIB_VSN = 3.14
diff --git a/lib/syntax_tools/doc/src/notes.xml b/lib/syntax_tools/doc/src/notes.xml
index f60d74d16b..b7d304aabe 100644
--- a/lib/syntax_tools/doc/src/notes.xml
+++ b/lib/syntax_tools/doc/src/notes.xml
@@ -32,6 +32,23 @@
<p>This document describes the changes made to the Syntax_Tools
application.</p>
+<section><title>Syntax_Tools 2.4</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ In the <c>syntax_tools</c> application, the <c>igor</c>
+ module and all functions in <c>erl_tidy</c> except
+ <c>file/2</c> have been deprecated.</p>
+ <p>
+ Own Id: OTP-17046</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Syntax_Tools 2.3.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/syntax_tools/src/erl_tidy.erl b/lib/syntax_tools/src/erl_tidy.erl
index d97afda0ea..fb312bb9d4 100644
--- a/lib/syntax_tools/src/erl_tidy.erl
+++ b/lib/syntax_tools/src/erl_tidy.erl
@@ -48,6 +48,11 @@
%% @type filename() = file:filename().
-module(erl_tidy).
+-deprecated([{dir,0,"use https://github.com/richcarl/erl_tidy"}]).
+-deprecated([{dir,1,"use https://github.com/richcarl/erl_tidy"}]).
+-deprecated([{file,1,"use https://github.com/richcarl/erl_tidy"}]).
+-deprecated([{module,1,"use https://github.com/richcarl/erl_tidy"}]).
+-deprecated([{module,2,"use https://github.com/richcarl/erl_tidy"}]).
-export([dir/0, dir/1, dir/2, file/1, file/2, module/1, module/2]).
diff --git a/lib/syntax_tools/src/igor.erl b/lib/syntax_tools/src/igor.erl
index b712b77e9f..ad48ddd50d 100644
--- a/lib/syntax_tools/src/igor.erl
+++ b/lib/syntax_tools/src/igor.erl
@@ -89,6 +89,8 @@
%% TODO: optionally rename all functions from specified (or all) modules.
-module(igor).
+-deprecated([{'_','_',"use https://github.com/richcarl/igor"}]).
+-compile({nowarn_deprecated_function,[{erl_tidy,module,2}]}).
-export([create_stubs/2, merge/2, merge/3, merge_files/3, merge_files/4,
merge_sources/3, parse_transform/2, rename/2, rename/3]).
diff --git a/lib/syntax_tools/vsn.mk b/lib/syntax_tools/vsn.mk
index 1f9cee6d18..7f0b8f40dd 100644
--- a/lib/syntax_tools/vsn.mk
+++ b/lib/syntax_tools/vsn.mk
@@ -1 +1 @@
-SYNTAX_TOOLS_VSN = 2.3.1
+SYNTAX_TOOLS_VSN = 2.4
diff --git a/lib/tools/doc/src/notes.xml b/lib/tools/doc/src/notes.xml
index 7ef27053be..5b98d338b0 100644
--- a/lib/tools/doc/src/notes.xml
+++ b/lib/tools/doc/src/notes.xml
@@ -31,6 +31,38 @@
</header>
<p>This document describes the changes made to the Tools application.</p>
+<section><title>Tools 3.4.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Correct the Xref analysis <c>undefined_functions</c> to
+ not report internally generated behaviour_info/1.</p>
+ <p>
+ Own Id: OTP-17191 Aux Id: OTP-16922, ERL-1476, GH-4192 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Tools 3.4.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Correct the Xref analysis <c>exports_not_used</c> to not
+ report internally generated <c>behaviour_info/1</c>.</p>
+ <p>
+ Own Id: OTP-16922 Aux Id: PR-2752 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Tools 3.4.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/tools/doc/src/xref.xml b/lib/tools/doc/src/xref.xml
index 33b8003320..72cc5c6c39 100644
--- a/lib/tools/doc/src/xref.xml
+++ b/lib/tools/doc/src/xref.xml
@@ -885,7 +885,9 @@ Evaluates a predefined analysis.
locally used.</item>
<tag><c>exports_not_used</c></tag>
<item>Returns a list of exported functions that have not been
- externally used.</item>
+ externally used. Note that in <c>modules</c> mode,
+ <c>M:behaviour_info/1</c> is never reported as unused.
+ </item>
<tag><c>deprecated_function_calls</c>(*)</tag>
<item>Returns a list of external calls to <seeerl marker="#deprecated_function">deprecated functions</seeerl>.</item>
<tag><c>{deprecated_function_calls, DeprFlag}</c>(*)</tag>
diff --git a/lib/tools/src/xref_reader.erl b/lib/tools/src/xref_reader.erl
index d28bdb78db..9857e8c150 100644
--- a/lib/tools/src/xref_reader.erl
+++ b/lib/tools/src/xref_reader.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2000-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2000-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -53,7 +53,8 @@
%% R8: abstract_v2
%% R9C: raw_abstract_v1
-%% -> {ok, Module, {DefAt, CallAt, LC, XC, X, Attrs}, Unresolved}} | EXIT
+%% -> {ok, Module, {DefAt, LCallAt, XCallAt, LC, XC, X, Attrs, Depr, OL},
+%% Unresolved}} | EXIT
%% Attrs = {ALC, AXC, Bad}
%% ALC, AXC and Bad are extracted from the attribute 'xref'. An experiment.
module(Module, Forms, CollectBuiltins, X, DF) ->
@@ -96,13 +97,18 @@ form({function, _, module_info, 0, _Clauses}, S) ->
form({function, _, module_info, 1, _Clauses}, S) ->
S;
form({function, Anno, Name, Arity, Clauses}, S) ->
- MFA0 = {S#xrefr.module, Name, Arity},
- MFA = adjust_arity(S, MFA0),
- S1 = S#xrefr{function = MFA},
- Line = erl_anno:line(Anno),
- S2 = S1#xrefr{def_at = [{MFA,Line} | S#xrefr.def_at]},
- S3 = clauses(Clauses, S2),
- S3#xrefr{function = []};
+ case {Name, Arity, erl_anno:location(Anno)} of
+ {behaviour_info, 1, 0} ->
+ S; % generated
+ _ ->
+ MFA0 = {S#xrefr.module, Name, Arity},
+ MFA = adjust_arity(S, MFA0),
+ S1 = S#xrefr{function = MFA},
+ Line = erl_anno:line(Anno),
+ S2 = S1#xrefr{def_at = [{MFA,Line} | S#xrefr.def_at]},
+ S3 = clauses(Clauses, S2),
+ S3#xrefr{function = []}
+ end;
form(_, S) ->
%% OTP 20. Other uninteresting forms such as {eof, _} and {warning, _}.
%% Exposed because sys_pre_expand is no longer run.
diff --git a/lib/tools/src/xref_utils.erl b/lib/tools/src/xref_utils.erl
index eca751337b..c516f272cb 100644
--- a/lib/tools/src/xref_utils.erl
+++ b/lib/tools/src/xref_utils.erl
@@ -327,7 +327,7 @@ list_dirs([], _I, _Exts, C, E) ->
%% Returns functions that are present in all modules.
predefined_functions() ->
- [{module_info,0}, {module_info,1}].
+ [{module_info,0}, {module_info,1}, {behaviour_info,1}].
%% Returns true if an MFA takes functional arguments.
is_funfun(erlang, apply, 2) -> true;
diff --git a/lib/tools/test/emacs_SUITE.erl b/lib/tools/test/emacs_SUITE.erl
index a65a131817..e5587b35b1 100644
--- a/lib/tools/test/emacs_SUITE.erl
+++ b/lib/tools/test/emacs_SUITE.erl
@@ -193,7 +193,7 @@ diff(Orig, File) ->
end.
emacs_version_ok(AcceptVer) ->
- VersionLine = os:cmd("emacs --version | head -1"),
+ VersionLine = os:cmd("emacs --version | head -n 1"),
io:format("~s~n", [VersionLine]),
case VersionLine of
"GNU Emacs " ++ Ver ->
diff --git a/lib/tools/test/xref_SUITE.erl b/lib/tools/test/xref_SUITE.erl
index c84f47c207..86dcb3c94d 100644
--- a/lib/tools/test/xref_SUITE.erl
+++ b/lib/tools/test/xref_SUITE.erl
@@ -48,7 +48,8 @@
fun_mfa/1, fun_mfa_r14/1,
fun_mfa_vars/1, qlc/1]).
--export([analyze/1, basic/1, md/1, q/1, variables/1, unused_locals/1]).
+-export([analyze/1, basic/1, md/1, q/1, variables/1, unused_locals/1,
+ behaviour/1]).
-export([format_error/1, otp_7423/1, otp_7831/1, otp_10192/1, otp_13708/1,
otp_14464/1, otp_14344/1]).
@@ -83,7 +84,7 @@ groups() ->
fun_mfa_r14, fun_mfa_vars, qlc]},
{analyses, [],
- [analyze, basic, md, q, variables, unused_locals]},
+ [analyze, basic, md, q, variables, unused_locals, behaviour]},
{misc, [], [format_error, otp_7423, otp_7831, otp_10192, otp_13708,
otp_14464, otp_14344]}].
@@ -2471,6 +2472,84 @@ otp_14344(Conf) when is_list(Conf) ->
ok = file:delete(File1),
ok = file:delete(Beam1).
+%% PR-2752, ERL-1353, ERL-1476, GH-4192.
+behaviour(Config) ->
+ ModMode = [{xref_mode, modules}],
+ FunMode = [],
+
+ Test1 = [{a, <<"-module(a).
+ -callback a() -> ok.
+ ">>}],
+ {Undef1, UnusedExports1} = behaviour_test(Test1, Config, FunMode),
+ [] = Undef1,
+ [] = UnusedExports1,
+ {Undef1m, UnusedExports1m} = behaviour_test(Test1, Config, ModMode),
+ [] = Undef1m,
+ [] = UnusedExports1m,
+
+ Test2 = [{a, <<"-module(a).
+ -export([behaviour_info/1]).
+ behaviour_info(_) ->
+ ok.
+ ">>}],
+ {Undef2, UnusedExports2} = behaviour_test(Test2, Config, FunMode),
+ [] = Undef2,
+ [{a,behaviour_info,1}] = UnusedExports2,
+ {Undef2m, UnusedExports2m} = behaviour_test(Test2, Config, ModMode),
+ [] = Undef2m,
+ %% Without abstract code it is not possible to determine if
+ %% M:behaviour_info/1 is generated or not. The best we can do is
+ %% to assume it is generated since it would otherwise always be
+ %% returned as unused.
+ [] = UnusedExports2m,
+
+ Test3 = [{a, <<"-module(a).
+ -export([behaviour_info/1]).
+ behaviour_info(_) ->
+ ok.
+ ">>},
+ {b, <<"-module(b).
+ -export([bar/0]).
+ bar() -> a:behaviour_info(callbacks).
+ ">>}],
+ {Undef3, UnusedExports3} = behaviour_test(Test3, Config, FunMode),
+ [] = Undef3,
+ [{b,bar,0}] = UnusedExports3,
+ {Undef3m, UnusedExports3m} = behaviour_test(Test3, Config, ModMode),
+ [] = Undef3m,
+ [{b,bar,0}] = UnusedExports3m,
+ ok.
+
+behaviour_test(Tests, Conf, Opts) ->
+ {ok, _} = xref:start(s, Opts),
+ add_modules(Tests, Conf),
+ case lists:keyfind(xref_mode, 1, Opts) of
+ {xref_mode, modules} ->
+ UndefinedFunctionCalls = [];
+ _ ->
+ {ok, UndefinedFunctionCalls} =
+ xref:analyze(s, undefined_function_calls)
+ end,
+ {ok, ExportsNotUsed} = xref:analyze(s, exports_not_used),
+ xref:stop(s),
+ {UndefinedFunctionCalls, ExportsNotUsed}.
+
+add_modules([], _Conf) ->
+ ok;
+add_modules([{Mod, Test} |Tests], Conf) ->
+ Dir = ?copydir,
+ Name = atom_to_list(Mod),
+ File = fname(Dir, Name ++ ".erl"),
+ MFile = fname(Dir, Name),
+ Beam = fname(Dir, Name ++ ".beam"),
+ ok = file:write_file(File, Test),
+ {ok, Mod} = compile:file(File, [debug_info,{outdir,Dir}]),
+ {ok, Mod} = xref:add_module(s, MFile),
+ check_state(s),
+ ok = file:delete(File),
+ ok = file:delete(Beam),
+ add_modules(Tests, Conf).
+
%%%
%%% Utilities
%%%
diff --git a/lib/tools/test/xref_SUITE_data/lib_test/bi.erl b/lib/tools/test/xref_SUITE_data/lib_test/bi.erl
new file mode 100644
index 0000000000..e083fa0f3c
--- /dev/null
+++ b/lib/tools/test/xref_SUITE_data/lib_test/bi.erl
@@ -0,0 +1,3 @@
+-module(bi).
+
+-callback a() -> ok.
diff --git a/lib/tools/test/xref_SUITE_data/lib_test/no_bi.erl b/lib/tools/test/xref_SUITE_data/lib_test/no_bi.erl
new file mode 100644
index 0000000000..5aac4a193e
--- /dev/null
+++ b/lib/tools/test/xref_SUITE_data/lib_test/no_bi.erl
@@ -0,0 +1,6 @@
+-module(no_bi).
+
+-export([behaviour_info/1]).
+
+behaviour_info(_) ->
+ ok.
diff --git a/lib/tools/vsn.mk b/lib/tools/vsn.mk
index 83222157b3..6e4ee2fd14 100644
--- a/lib/tools/vsn.mk
+++ b/lib/tools/vsn.mk
@@ -1 +1 @@
-TOOLS_VSN = 3.4.1
+TOOLS_VSN = 3.4.3
diff --git a/lib/wx/api_gen/wx_extra/wxTaskBarIcon.c_src b/lib/wx/api_gen/wx_extra/wxTaskBarIcon.c_src
new file mode 100644
index 0000000000..1b1f37486c
--- /dev/null
+++ b/lib/wx/api_gen/wx_extra/wxTaskBarIcon.c_src
@@ -0,0 +1,46 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2011-2016. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+<<wxTaskBarIcon_class
+class EwxTaskBarIcon : public wxTaskBarIcon {
+ public: ~EwxTaskBarIcon() {((WxeApp *)wxTheApp)->clearPtr(this);};
+ EwxTaskBarIcon() : wxTaskBarIcon() {};
+
+ int createPopupMenu;
+ ErlDrvTermData port;
+
+ private:
+ virtual wxMenu* CreatePopupMenu();
+};
+wxTaskBarIcon_class>>
+
+<<wxTaskBarIcon_new
+case ~s: { // wxTaskBarIcon::wxTaskBarIcon
+ EwxTaskBarIcon * Result = new EwxTaskBarIcon();
+ Result->port = Ecmd.port;
+ if ( * (int*) bp) {
+ Result->createPopupMenu = *(int *) bp;
+ }
+
+ newPtr((void *) Result, 1, memenv);
+ rt.addRef(getRef((void *)Result,memenv), "wxTaskBarIcon");
+ break;
+}
+wxTaskBarIcon_new>>
diff --git a/lib/wx/api_gen/wx_extra/wxTaskBarIcon.erl b/lib/wx/api_gen/wx_extra/wxTaskBarIcon.erl
new file mode 100644
index 0000000000..cde9c5ead6
--- /dev/null
+++ b/lib/wx/api_gen/wx_extra/wxTaskBarIcon.erl
@@ -0,0 +1,45 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2011-2016. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+<<EXPORT:wxTaskBarIcon new/0, new/1 wxTaskBarIcon:EXPORT>>
+
+<<wxTaskBarIcon_new
+%% @doc See <a href="http://www.wxwidgets.org/manuals/2.8.12/wx_wxtaskbaricon.html#wxtaskbariconwxtaskbaricon">external documentation</a>.
+-spec new() -> wxTaskBarIcon().
+new() ->
+ wxe_util:construct(~s, <<0:32>>).
+
+
+%% @doc Creates a TaskBarIcon with a callback function for CreatePopupMenu:
+%% <pre>Callback() -> term()</pre>
+%%
+%% See <a href="http://www.wxwidgets.org/manuals/2.8.12/wx_wxtaskbaricon.html#wxtaskbariconwxtaskbaricon">external documentation</a>.
+-spec new([Option]) -> wxTaskBarIcon() when
+ Option :: {'createPopupMenu', fun(() -> wxMenu:wxMenu())}.
+new([]) ->
+ new();
+new([{createPopupMenu, F}]) when is_function(F) ->
+ Fun = fun([_]) ->
+ #wx_ref{type=wxMenu,ref=ThisRef} = F(),
+ <<ThisRef:32/?UI>>
+ end,
+ BinFun = <<(wxe_util:get_cbId(Fun)):32/?UI, 0:32>>,
+ wxe_util:construct(?wxTaskBarIcon_new, BinFun).
+wxTaskBarIcon_new>>
diff --git a/lib/wx/api_gen/wx_gen.erl b/lib/wx/api_gen/wx_gen.erl
index d9dd624ca8..16b85be97d 100644
--- a/lib/wx/api_gen/wx_gen.erl
+++ b/lib/wx/api_gen/wx_gen.erl
@@ -1434,7 +1434,7 @@ extract_def([#xmlElement{name=name,content=[#xmlText{value=Name}]}|R], _N, Skip)
extract_def([#xmlElement{name=param}|_],Name,_) ->
throw(Name);
extract_def([#xmlElement{name=initializer,content=Cs}|_R],N,Skip) ->
- Val0 = extract_def2(Cs),
+ Val0 = string:strip(strip_comment(extract_def2(Cs))),
case Val0 of
"0x" ++ Val1 -> {N, list_to_integer(Val1, 16)};
_ ->
@@ -1458,7 +1458,7 @@ extract_def(_,N,_) ->
throw(N).
extract_def2([#xmlText{value=Val}|R]) ->
- string:strip(strip_comment(Val)) ++ extract_def2(R);
+ string:strip(Val) ++ extract_def2(R);
extract_def2([#xmlElement{content=Cs}|R]) ->
extract_def2(Cs) ++ extract_def2(R);
extract_def2([]) -> [].
diff --git a/lib/wx/api_gen/wx_gen_erl.erl b/lib/wx/api_gen/wx_gen_erl.erl
index d029ae77cb..e8287c4b1f 100644
--- a/lib/wx/api_gen/wx_gen_erl.erl
+++ b/lib/wx/api_gen/wx_gen_erl.erl
@@ -49,8 +49,10 @@ gen(Defs) ->
Replace = fun(C=#class{name=Name}, Dfs) ->
lists:keyreplace(Name, #class.name, Dfs, C)
end,
- [gen_class(Class) || Class <- lists:foldl(Replace, Defs, Static)],
- gen_funcnames().
+ All = lists:foldl(Replace, Defs, Static),
+ [gen_class(Class) || Class <- All],
+ gen_funcnames(),
+ gen_api_footprint(All).
gen_class(Class) ->
try
@@ -1324,6 +1326,57 @@ gen_funcnames() ->
[w("-define(~s_~s, ~p).~n", [Class,Name,Id]) || {Class,Name,_,Id} <- Ns],
close().
+gen_api_footprint(All) ->
+ %% To be able to diff content between old and new generators.
+ open_write("wx_28_api.dump"),
+ TF = fun TF (Type) ->
+ try wx_gen:type_foot_print(Type) of
+ {class, C} -> list_to_atom(C);
+ {merged, MTs} -> [TF(MT) || {_, MT} <- MTs];
+ T -> T
+ catch _:R:ST ->
+ case Type of
+ void -> ok;
+ {merged, _,T1,_, _,T2,_} ->
+ lists:sort([TF(T1),TF(T2)]);
+ _ ->
+ io:format("ERROR: ~p~n ~p~n",[R,ST]),
+ exit(R)
+ end
+ end
+ end,
+ Out = fun(T, []) -> TF(T);
+ (void, [#param{type=T}]) -> TF(T);
+ (void, Ps) -> list_to_tuple([TF(T) || #param{type=T} <- Ps]);
+ (T, Ps) -> list_to_tuple([TF(T)|[TF(Tp) || #param{type=Tp} <- Ps]])
+ end,
+ Methods = fun(CName, Parent, #method{name=N, alias=Alias, method_type=MType,where=W,type=Type0,params=Ps}) ->
+ {Args0,Opts0} = split_optional(Ps),
+ Type = Out(Type0, [P || P=#param{in=In} <- Ps,In =/= true]),
+ Args = [TF(PT) || #param{type=PT} <- Args0],
+ case W of
+ merged_c ->
+ ignore;
+ erl_no_opt ->
+ {CName, list_to_atom(erl_func_name(N,Alias)), Type, Args, [], W, Parent};
+ _ ->
+ Opts = lists:sort([{list_to_atom(erl_option_name(ON)), TF(OT)} ||
+ #param{name=ON,type=OT} <- Opts0]),
+ {CName, list_to_atom(erl_func_name(N,Alias)), Type, Args, Opts, W, Parent}
+ end
+ end,
+ Class = fun(#class{name=CName0,parent=Parent,methods=Ms0,options=Opts}) ->
+ CName = case Parent of
+ "static" -> "wx_misc";
+ _ -> CName0
+ end,
+ [Methods(list_to_atom(CName), list_to_atom(Parent), M) || Ms <- Ms0, M <-Ms]
+ end,
+ Sorted = lists:sort(lists:append([Class(C) || C <- All])),
+ [w("~0p.~n", [E]) || E <- Sorted, E /= ignore],
+ close().
+
+
get_unique_name(ID) when is_integer(ID) ->
Tree = get(unique_names),
{Class,Name, _,_} = gb_trees:get(ID, Tree),
diff --git a/lib/wx/api_gen/wxapi.conf b/lib/wx/api_gen/wxapi.conf
index cd53a25c40..d2f8cac4c1 100644
--- a/lib/wx/api_gen/wxapi.conf
+++ b/lib/wx/api_gen/wxapi.conf
@@ -2005,7 +2005,7 @@
[wxLogNull, '~wxLogNull']}.
{class, wxTaskBarIcon, wxEvtHandler, [],
- [wxTaskBarIcon,'~wxTaskBarIcon',
+ [{wxTaskBarIcon, [{where, taylormade}]},'~wxTaskBarIcon',
%%'CreatePopupMenu', virtual overrided is a callback
%% 'IsIconInstalled', 'IsOk', not available on mac
'PopupMenu','RemoveIcon','SetIcon']}.
diff --git a/lib/wx/c_src/gen/wxe_derived_dest.h b/lib/wx/c_src/gen/wxe_derived_dest.h
index a7114eb188..52a1460a79 100644
--- a/lib/wx/c_src/gen/wxe_derived_dest.h
+++ b/lib/wx/c_src/gen/wxe_derived_dest.h
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2008-2018. All Rights Reserved.
+ * Copyright Ericsson AB 2008-2020. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -766,9 +766,16 @@ class EwxHtmlWindow : public wxHtmlWindow {
EwxHtmlWindow() : wxHtmlWindow() {};
};
+
class EwxTaskBarIcon : public wxTaskBarIcon {
public: ~EwxTaskBarIcon() {((WxeApp *)wxTheApp)->clearPtr(this);};
EwxTaskBarIcon() : wxTaskBarIcon() {};
+
+ int createPopupMenu;
+ ErlDrvTermData port;
+
+ private:
+ virtual wxMenu* CreatePopupMenu();
};
class EwxLocale : public wxLocale {
diff --git a/lib/wx/c_src/gen/wxe_funcs.cpp b/lib/wx/c_src/gen/wxe_funcs.cpp
index 747201fe78..4304cdda91 100644
--- a/lib/wx/c_src/gen/wxe_funcs.cpp
+++ b/lib/wx/c_src/gen/wxe_funcs.cpp
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2008-2019. All Rights Reserved.
+ * Copyright Ericsson AB 2008-2020. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31773,8 +31773,14 @@ case wxLogNull_destroy: { // wxLogNull::destroy
delete This;}
break;
}
+
case wxTaskBarIcon_new: { // wxTaskBarIcon::wxTaskBarIcon
- wxTaskBarIcon * Result = new EwxTaskBarIcon();
+ EwxTaskBarIcon * Result = new EwxTaskBarIcon();
+ Result->port = Ecmd.port;
+ if ( * (int*) bp) {
+ Result->createPopupMenu = *(int *) bp;
+ }
+
newPtr((void *) Result, 1, memenv);
rt.addRef(getRef((void *)Result,memenv), "wxTaskBarIcon");
break;
diff --git a/lib/wx/c_src/wxe_callback_impl.cpp b/lib/wx/c_src/wxe_callback_impl.cpp
index 77359e9256..09c9f2630a 100644
--- a/lib/wx/c_src/wxe_callback_impl.cpp
+++ b/lib/wx/c_src/wxe_callback_impl.cpp
@@ -309,6 +309,26 @@ int wxCALLBACK wxEListCtrlCompare(wxeIntPtr item1, wxeIntPtr item2, wxeIntPtr ca
}
+/* *****************************************************************/
+// TaskBarIcon with callbacks for VIRTUAL_TABLES
+
+wxMenu* EwxTaskBarIcon::CreatePopupMenu() {
+ if(createPopupMenu) {
+ INVOKE_CALLBACK(port, createPopupMenu, "wxTaskBarIcon");
+ char * bp = ((WxeApp *) wxTheApp)->cb_buff;
+ wxeMemEnv * memenv = ((WxeApp *) wxTheApp)->getMemEnv(port);
+ if(bp) {
+ wxMenu * result = (wxMenu *)((WxeApp *) wxTheApp)->getPtr(bp, memenv);
+ driver_free(((WxeApp *) wxTheApp)->cb_buff);
+ ((WxeApp *) wxTheApp)->cb_buff = NULL;
+ return result;
+ }
+ }
+ return NULL;
+}
+
+
+
// tools
void clear_cb(ErlDrvTermData port, int callback)
diff --git a/lib/wx/c_src/wxe_main.cpp b/lib/wx/c_src/wxe_main.cpp
index 5b65d8a59b..fdee1fb1c0 100644
--- a/lib/wx/c_src/wxe_main.cpp
+++ b/lib/wx/c_src/wxe_main.cpp
@@ -118,7 +118,20 @@ void *wxe_main_loop(void *vpdl)
{
int result;
int argc = 1;
- const wxChar temp[10] = L"Erlang";
+ wxChar temp[128] = L"Erlang";
+
+ size_t app_len = 127;
+ char app_title_buf[128];
+ int res = erl_drv_getenv("WX_APP_TITLE", app_title_buf, &app_len);
+ if (res == 0) {
+ wxString title = wxString::FromUTF8(app_title_buf);
+ int size = title.Length() < 127 ? title.Length() : 126;
+ for(int i = 0; i < size; i++) {
+ temp[i] = title[i];
+ }
+ temp[size] = 0;
+ }
+
wxChar * argv[] = {(wxChar *)temp, NULL};
ErlDrvPDL pdl = (ErlDrvPDL) vpdl;
diff --git a/lib/wx/c_src/wxe_ps_init.c b/lib/wx/c_src/wxe_ps_init.c
index 62c7c51c13..d82d142967 100644
--- a/lib/wx/c_src/wxe_ps_init.c
+++ b/lib/wx/c_src/wxe_ps_init.c
@@ -74,7 +74,7 @@ void * wxe_ps_init2() {
if( !is_packaged_app() ) {
// Undocumented function (but no documented way of doing this exists)
int res = erl_drv_getenv("WX_APP_TITLE", app_title_buf, &app_len);
- if (res >= 0) {
+ if (res == 0) {
app_title = app_title_buf;
} else {
app_title = NULL;
diff --git a/lib/wx/doc/src/notes.xml b/lib/wx/doc/src/notes.xml
index b9012054a8..fe5a9966f8 100644
--- a/lib/wx/doc/src/notes.xml
+++ b/lib/wx/doc/src/notes.xml
@@ -32,6 +32,21 @@
<p>This document describes the changes made to the wxErlang
application.</p>
+<section><title>Wx 1.9.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Add popup menu callback to <c>wxTaskBarIcon:new/1</c>.</p>
+ <p>
+ Own Id: OTP-16983 Aux Id: PR-2743 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Wx 1.9.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/wx/examples/simple/menu.erl b/lib/wx/examples/simple/menu.erl
index 7c0400bd1e..a81a1a6efa 100644
--- a/lib/wx/examples/simple/menu.erl
+++ b/lib/wx/examples/simple/menu.erl
@@ -90,7 +90,10 @@
start() ->
Wx = wx:new(),
Frame = wx:batch(fun() -> create_frame(Wx) end),
+ Taskbar = wxTaskBarIcon:new([{createPopupMenu, fun() -> create_dummy_menu() end}]),
wxWindow:show(Frame),
+ Path = filename:dirname(code:which(?MODULE)),
+ wxTaskBarIcon:setIcon(Taskbar, wxIcon:new(filename:join(Path,"sample.xpm"), [{type, ?wxBITMAP_TYPE_XPM}])),
State = #state{},
diff --git a/lib/wx/src/gen/wxTaskBarIcon.erl b/lib/wx/src/gen/wxTaskBarIcon.erl
index af4859fb88..2f06d906ea 100644
--- a/lib/wx/src/gen/wxTaskBarIcon.erl
+++ b/lib/wx/src/gen/wxTaskBarIcon.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@
-module(wxTaskBarIcon).
-include("wxe.hrl").
--export([destroy/1,new/0,popupMenu/2,removeIcon/1,setIcon/2,setIcon/3]).
+-export([ new/0, new/1 ,destroy/1,popupMenu/2,removeIcon/1,setIcon/2,setIcon/3]).
%% inherited exports
-export([connect/2,connect/3,disconnect/1,disconnect/2,disconnect/3,parent_class/1]).
@@ -39,12 +39,28 @@ parent_class(wxEvtHandler) -> true;
parent_class(_Class) -> erlang:error({badtype, ?MODULE}).
-type wxTaskBarIcon() :: wx:wx_object().
+
%% @doc See <a href="http://www.wxwidgets.org/manuals/2.8.12/wx_wxtaskbaricon.html#wxtaskbariconwxtaskbaricon">external documentation</a>.
-spec new() -> wxTaskBarIcon().
new() ->
- wxe_util:construct(?wxTaskBarIcon_new,
- <<>>).
+ wxe_util:construct(?wxTaskBarIcon_new, <<0:32>>).
+
+%% @doc Creates a TaskBarIcon with a callback function for CreatePopupMenu:
+%% <pre>Callback() -> term()</pre>
+%%
+%% See <a href="http://www.wxwidgets.org/manuals/2.8.12/wx_wxtaskbaricon.html#wxtaskbariconwxtaskbaricon">external documentation</a>.
+-spec new([Option]) -> wxTaskBarIcon() when
+ Option :: {'createPopupMenu', fun(() -> wxMenu:wxMenu())}.
+new([]) ->
+ new();
+new([{createPopupMenu, F}]) when is_function(F) ->
+ Fun = fun([_]) ->
+ #wx_ref{type=wxMenu,ref=ThisRef} = F(),
+ <<ThisRef:32/?UI>>
+ end,
+ BinFun = <<(wxe_util:get_cbId(Fun)):32/?UI, 0:32>>,
+ wxe_util:construct(?wxTaskBarIcon_new, BinFun).
%% @doc See <a href="http://www.wxwidgets.org/manuals/2.8.12/wx_wxtaskbaricon.html#wxtaskbariconpopupmenu">external documentation</a>.
-spec popupMenu(This, Menu) -> boolean() when
This::wxTaskBarIcon(), Menu::wxMenu:wxMenu().
diff --git a/lib/wx/vsn.mk b/lib/wx/vsn.mk
index 552e09ee2a..e2ff4812e9 100644
--- a/lib/wx/vsn.mk
+++ b/lib/wx/vsn.mk
@@ -1 +1 @@
-WX_VSN = 1.9.1
+WX_VSN = 1.9.2
diff --git a/lib/xmerl/doc/src/notes.xml b/lib/xmerl/doc/src/notes.xml
index d8b2852097..143111e3d1 100644
--- a/lib/xmerl/doc/src/notes.xml
+++ b/lib/xmerl/doc/src/notes.xml
@@ -32,6 +32,29 @@
<p>This document describes the changes made to the Xmerl application.</p>
+<section><title>Xmerl 1.3.26</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Corrected namespace and expanded name in the old dom
+ backend example module.</p>
+ <p>
+ Own Id: OTP-17060</p>
+ </item>
+ <item>
+ <p>
+ Corrected a bug that in some cases didn't allow
+ unresolved references when skip_external_dtd option used.</p>
+ <p>
+ Own Id: OTP-17061</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Xmerl 1.3.25</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/xmerl/src/xmerl_sax_old_dom.erl b/lib/xmerl/src/xmerl_sax_old_dom.erl
index 6d0d836487..b3b2727082 100644
--- a/lib/xmerl/src/xmerl_sax_old_dom.erl
+++ b/lib/xmerl/src/xmerl_sax_old_dom.erl
@@ -1,9 +1,9 @@
%%-*-erlang-*-
%%--------------------------------------------------------------------
%% %CopyrightBegin%
-%%
+%%
%% Copyright Ericsson AB 2009-2017. All Rights Reserved.
-%%
+%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
@@ -15,13 +15,13 @@
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
-%%
+%%
%% %CopyrightEnd%
%%----------------------------------------------------------------------
%% File : xmerl_sax_old_dom.erl
-%% Description :
+%% Description :
%%
-%% Created : 02 Oct 2008
+%% Created : 02 Oct 2008
%%----------------------------------------------------------------------
-module(xmerl_sax_old_dom).
@@ -52,7 +52,7 @@
%%----------------------------------------------------------------------
%% Error handling
%%----------------------------------------------------------------------
--define(error(Reason),
+-define(error(Reason),
throw({xmerl_sax_old_dom_error, Reason})).
%%======================================================================
@@ -66,7 +66,7 @@
tags=[], %% Tag stack
cno=[], %% Current node number
namespaces = [], %% NameSpace stack
- dom=[] %% DOM structure
+ dom=[] %% DOM structure
}).
%%======================================================================
@@ -74,8 +74,8 @@
%%======================================================================
%%----------------------------------------------------------------------
%% Function: initial_state() -> Result
-%% Parameters:
-%% Result:
+%% Parameters:
+%% Result:
%% Description:
%%----------------------------------------------------------------------
initial_state() ->
@@ -83,8 +83,8 @@ initial_state() ->
%%----------------------------------------------------------------------
%% Function: get_dom(State) -> Result
-%% Parameters:
-%% Result:
+%% Parameters:
+%% Result:
%% Description:
%%----------------------------------------------------------------------
get_dom(#xmerl_sax_old_dom_state{dom=Dom}) ->
@@ -92,8 +92,8 @@ get_dom(#xmerl_sax_old_dom_state{dom=Dom}) ->
%%----------------------------------------------------------------------
%% Function: event(Event, LineNo, State) -> Result
-%% Parameters:
-%% Result:
+%% Parameters:
+%% Result:
%% Description:
%%----------------------------------------------------------------------
event(Event, _LineNo, State) ->
@@ -109,14 +109,14 @@ event(Event, _LineNo, State) ->
%% Parameters: Event = term()
%% State = #xmerl_sax_old_dom_state{}
%% Result : #xmerl_sax_old_dom_state{} |
-%% Description:
+%% Description:
%%----------------------------------------------------------------------
-%% Document
%%----------------------------------------------------------------------
+%% Document
build_dom(startDocument, State) ->
State#xmerl_sax_old_dom_state{dom=[startDocument]};
-build_dom(endDocument,
+build_dom(endDocument,
#xmerl_sax_old_dom_state{dom=[#xmlElement{content=C} = Current |D]} = State) ->
case D of
[startDocument] ->
@@ -127,18 +127,17 @@ build_dom(endDocument,
State#xmerl_sax_old_dom_state{dom=[Decl, Current#xmlElement{
content=lists:reverse(C)
}]};
- _ ->
- %% endDocument is also sent by the parser when a fault occur to tell
+ _ ->
+ %% endDocument is also sent by the parser when a fault occur to tell
%% the event receiver that no more input will be sent
State
end;
-%% Element
%%----------------------------------------------------------------------
-build_dom({startElement, Uri, LocalName, QName, Attributes},
- #xmerl_sax_old_dom_state{tags=T, cno=CN, namespaces=NS, dom=D} = State) ->
+%% Element
+build_dom({startElement, Uri, LocalName, QName, Attributes},
+ #xmerl_sax_old_dom_state{tags=T, cno=CN, namespaces=NS0, dom=D} = State) ->
- A = parse_attributes(LocalName, Attributes),
{Num, NewCN} =
case CN of
[] ->
@@ -147,32 +146,43 @@ build_dom({startElement, Uri, LocalName, QName, Attributes},
{N, [1, N+1 |CNs]}
end,
- NsInfo =
+ NsInfo =
case QName of
{[], _} -> [];
QN -> QN
end,
- NameAsAtom = convert_qname_to_atom(QName),
+ NameAsAtom = convert_qname_to_atom(QName),
+ ExpandedName = convert_to_expanded_name(Uri, LocalName),
+ DefaultNS =
+ case lists:keyfind([], 1, NS0) of
+ false ->
+ [];
+ {_, Default} -> Default
+ end,
+ NS1 = lists:filter(fun({[], _}) -> false; ({_,_}) -> true end, NS0),
+ NameSpace = #xmlNamespace{default=DefaultNS, nodes=NS1},
+ NewTagsList = [{NameAsAtom, Num} |T],
- State#xmerl_sax_old_dom_state{tags=[{NameAsAtom, Num} |T],
+ A = parse_attributes(Attributes, LocalName, NameSpace, NewTagsList),
+
+ State#xmerl_sax_old_dom_state{tags=NewTagsList,
cno=NewCN,
- dom=[#xmlElement{name=NameAsAtom,
- expanded_name=NameAsAtom,
+ dom=[#xmlElement{name=NameAsAtom,
+ expanded_name=ExpandedName,
nsinfo=NsInfo,
- namespace=#xmlNamespace{default=list_to_atom(Uri),
- nodes=NS},
+ namespace=NameSpace,
pos=Num,
parents=T,
attributes=lists:reverse(A),
xmlbase="."
} | D]};
-build_dom({endElement, _Uri, LocalName, QName},
+build_dom({endElement, _Uri, LocalName, QName},
#xmerl_sax_old_dom_state{tags=[_ |T],
cno=[_ |CN],
- dom=[#xmlElement{name=CName, content=C} = Current,
+ dom=[#xmlElement{name=CName, content=C} = Current,
#xmlElement{content=PC} = Parent | D]} = State) ->
case convert_qname_to_atom(QName) of
- CName ->
+ CName ->
State#xmerl_sax_old_dom_state{tags=T,
cno=CN,
dom=[Parent#xmlElement{
@@ -182,51 +192,49 @@ build_dom({endElement, _Uri, LocalName, QName},
|PC]
} | D]};
_ ->
- ?error("Got end of element: " ++ LocalName ++ " but expected: " ++
+ ?error("Got end of element: " ++ LocalName ++ " but expected: " ++
Current#xmlElement.name)
end;
-%% Text
%%----------------------------------------------------------------------
+%% Text
build_dom({characters, String},
- #xmerl_sax_old_dom_state{tags=T,
+ #xmerl_sax_old_dom_state{tags=T,
cno=[Num |CN],
dom=[#xmlElement{content=C} = Current| D]} = State) ->
- State#xmerl_sax_old_dom_state{cno=[Num+1 |CN],
+ State#xmerl_sax_old_dom_state{cno=[Num+1 |CN],
dom=[Current#xmlElement{content=[#xmlText{value=String, parents=T, pos=Num, type=text}
|C]} | D]};
build_dom({ignorableWhitespace, String},
- #xmerl_sax_old_dom_state{tags=T,
+ #xmerl_sax_old_dom_state{tags=T,
cno=[Num |CN],
dom=[#xmlElement{content=C} = Current| D]} = State) ->
State#xmerl_sax_old_dom_state{cno=[Num+1 |CN],
- dom=[Current#xmlElement{content=[#xmlText{value=String,
- parents=T, pos=Num,
+ dom=[Current#xmlElement{content=[#xmlText{value=String,
+ parents=T, pos=Num,
type=text}
|C]} | D]};
-%% Comments
%%----------------------------------------------------------------------
+%% Comments
build_dom({comment, String},
- #xmerl_sax_old_dom_state{tags=T,
+ #xmerl_sax_old_dom_state{tags=T,
cno=[Num |CN],
dom=[#xmlElement{content=C} = Current| D]} = State) ->
State#xmerl_sax_old_dom_state{cno=[Num+1 |CN],
dom=[Current#xmlElement{content=[#xmlComment{parents=T, pos=Num, value=String}|C]} | D]};
-%% NameSpaces
%%----------------------------------------------------------------------
-build_dom({startPrefixMapping, [], _Uri}, State) ->
- State;
+%% NameSpaces
build_dom({startPrefixMapping, Prefix, Uri},
- #xmerl_sax_old_dom_state{namespaces=NS} = State) ->
+ #xmerl_sax_old_dom_state{namespaces=NS} = State) ->
State#xmerl_sax_old_dom_state{namespaces=[{Prefix, list_to_atom(Uri)} |NS]};
build_dom({endPrefixMapping, Prefix},
- #xmerl_sax_old_dom_state{namespaces=[{Prefix, _} |NS]} = State) ->
- State#xmerl_sax_old_dom_state{namespaces=NS};
+ #xmerl_sax_old_dom_state{namespaces=NS} = State) ->
+ State#xmerl_sax_old_dom_state{namespaces=lists:keydelete(Prefix, 1, NS)};
-%% Processing instructions
%%----------------------------------------------------------------------
+%% Processing instructions
build_dom({processingInstruction,"xml", PiData},
#xmerl_sax_old_dom_state{dom=D} = State) ->
{Vsn, PiData1} = find_and_remove_attribute("version", PiData, []),
@@ -236,44 +244,42 @@ build_dom({processingInstruction,"xml", PiData},
build_dom({processingInstruction, PiTarget, PiData},
#xmerl_sax_old_dom_state{cno=[Num |CN],
dom=[#xmlElement{content=C} = Current| D]} = State) ->
- State#xmerl_sax_old_dom_state{cno=[Num+1 |CN],
+ State#xmerl_sax_old_dom_state{cno=[Num+1 |CN],
dom=[Current#xmlElement{content=[#xmlPI{name=PiTarget,pos=Num, value=PiData}
|C]} | D]};
-%% Default
%%----------------------------------------------------------------------
+%% Default
build_dom(_E, State) ->
- State.
-
+ State.
%%----------------------------------------------------------------------
-%% Function : parse_attributes(ElName, Attributes) -> Result
-%% Parameters:
-%% Result :
-%% Description:
+%% Function : parse_attributes/2
%%----------------------------------------------------------------------
-parse_attributes(ElName, Attributes) ->
- parse_attributes(ElName, Attributes, 1, []).
+parse_attributes(Attributes, ElName, NameSpace, T) ->
+ parse_attributes(Attributes, ElName, NameSpace, T, 1 , []).
-parse_attributes(_, [], _, Acc) ->
+parse_attributes([], _, _, _, _, Acc) ->
Acc;
-parse_attributes(ElName, [{_Uri, Prefix, LocalName, AttrValue} |As], N, Acc) ->
+parse_attributes([{Uri, Prefix, LocalName, AttrValue} |As], ElName, NameSpace, T, N, Acc) ->
Name = convert_qname_to_atom({Prefix,LocalName}),
- NsInfo =
+ NsInfo =
case Prefix of
[] -> [];
P -> {P,LocalName}
end,
- parse_attributes(ElName, As, N+1, [#xmlAttribute{name=Name,
- pos=N,
- nsinfo=NsInfo,
- value=AttrValue,
- normalized=false} |Acc]).
+ ExpandedName = convert_to_expanded_name(Uri, LocalName),
+ parse_attributes(As, ElName, NameSpace, T, N+1,
+ [#xmlAttribute{name=Name,
+ expanded_name=ExpandedName,
+ nsinfo=NsInfo,
+ namespace=NameSpace,
+ parents=T,
+ pos=N,
+ value=AttrValue,
+ normalized=false} |Acc]).
%%----------------------------------------------------------------------
%% Function : convert_qname_to_atom(QName) -> Result
-%% Parameters:
-%% Result :
-%% Description:
%%----------------------------------------------------------------------
convert_qname_to_atom({[], N}) ->
list_to_atom(N);
@@ -281,10 +287,15 @@ convert_qname_to_atom({P,N}) ->
list_to_atom(P ++ ":" ++ N).
%%----------------------------------------------------------------------
-%% Function : find_and_remove_attribute(Key, Data, Default) -> Result
-%% Parameters:
-%% Result :
-%% Description:
+%% Function : convert_to_expanded_name/2
+%%----------------------------------------------------------------------
+convert_to_expanded_name([], LocalName) ->
+ list_to_atom(LocalName);
+convert_to_expanded_name(Uri, LocalName) ->
+ {list_to_atom(Uri), list_to_atom(LocalName)}.
+
+%%----------------------------------------------------------------------
+%% Function : find_and_remove_attribute/3
%%----------------------------------------------------------------------
find_and_remove_attribute(Key, Data, Default) ->
case lists:keysearch(Key, 1, Data) of
diff --git a/lib/xmerl/src/xmerl_sax_parser_base.erlsrc b/lib/xmerl/src/xmerl_sax_parser_base.erlsrc
index 297ef484fd..3dfdec440a 100644
--- a/lib/xmerl/src/xmerl_sax_parser_base.erlsrc
+++ b/lib/xmerl/src/xmerl_sax_parser_base.erlsrc
@@ -1209,7 +1209,12 @@ parse_att_value(?STRING_REST("&", Rest), State, Stop, Acc) ->
{external_general, Name, _} ->
?fatal_error(State1, "External parsed entity reference in attribute value: " ++ Name);
{not_found, Name} when State#xmerl_sax_parser_state.file_type =:= normal ->
- ?fatal_error(State1, "Undeclared reference: " ++ Name);
+ case State1#xmerl_sax_parser_state.skip_external_dtd of
+ false ->
+ ?fatal_error(State1, "Undeclared reference: " ++ Name);
+ true ->
+ parse_att_value(Rest1, State1, Stop, ";" ++ lists:reverse(Name) ++ "&" ++ Acc)
+ end;
{not_found, Name} ->
parse_att_value(Rest1, State1, Stop, ";" ++ lists:reverse(Name) ++ "&" ++ Acc);
{unparsed, Name, _} ->
diff --git a/lib/xmerl/vsn.mk b/lib/xmerl/vsn.mk
index 8711ed946f..5715f32e4f 100644
--- a/lib/xmerl/vsn.mk
+++ b/lib/xmerl/vsn.mk
@@ -1 +1 @@
-XMERL_VSN = 1.3.25
+XMERL_VSN = 1.3.26
diff --git a/make/app_targets.mk b/make/app_targets.mk
index e9aaa4193c..265fb43519 100644
--- a/make/app_targets.mk
+++ b/make/app_targets.mk
@@ -49,7 +49,7 @@ $(DIA_PLT_DIR):
$(DIA_PLT): $(DIA_PLT_DIR)
@echo "Building $(APPLICATION) plt file"
- @$(ERL_TOP)/bin/dialyzer --build_plt \
+ @dialyzer --build_plt \
--output_plt $@ \
--apps $(sort $(DIA_PLT_APPS) $(DIA_DEFAULT_PLT_APPS)) \
--output $(DIA_ANALYSIS) \
diff --git a/make/configure.in b/make/configure.in
index bf6ee28434..d603c9d202 100644
--- a/make/configure.in
+++ b/make/configure.in
@@ -76,13 +76,13 @@ esac
#
# Now srcdir is absolute and also the top of Erlang distribution, ERL_TOP.
#
-test "X$ERL_TOP" != "X" || ERL_TOP="$srcdir"
+test "X$ERL_TOP" != "X" || AC_MSG_ERROR([ERL_TOP not set])
AC_SUBST(ERL_TOP)
dnl
dnl Aux programs are found in erts/autoconf
dnl
-AC_CONFIG_AUX_DIR(${srcdir}/erts/autoconf)
+AC_CONFIG_AUX_DIR(${ERL_TOP}/erts/autoconf)
dnl
dnl Figure out what we are running on. And in violation of autoconf
@@ -387,50 +387,6 @@ if test X${enable_native_libs} = Xyes -a X${enable_hipe} != Xno; then
fi
AC_SUBST(NATIVE_LIBS_ENABLED)
-if test $CROSS_COMPILING = no; then
- case $host_os in
- darwin*)
- macosx_version=`sw_vers -productVersion`
- test $? -eq 0 || {
- AC_MSG_ERROR([Failed to execute 'sw_vers'; please provide it in PATH])
- }
- [case "$macosx_version" in
- [1-9][0-9].[0-9])
- int_macosx_version=`echo $macosx_version | sed 's|\([^\.]*\)\.\([^\.]*\)|\10\200|'`;;
- [1-9][0-9].[0-9].[0-9])
- int_macosx_version=`echo $macosx_version | sed 's|\([^\.]*\)\.\([^\.]*\)\.\([^\.]*\)|\1\2\3|'`;;
- [1-9][0-9].[1-9][0-9])
- int_macosx_version=`echo $macosx_version | sed 's|\([^\.]*\)\.\([^\.]*\)|\1\200|'`;;
- [1-9][0-9].[1-9][0-9].[0-9])
- int_macosx_version=`echo $macosx_version | sed 's|\([^\.]*\)\.\([^\.]*\)\.\([^\.]*\)|\1\20\3|'`;;
- [1-9][0-9].[1-9][0-9].[1-9][0-9])
- int_macosx_version=`echo $macosx_version | sed 's|\([^\.]*\)\.\([^\.]*\)\.\([^\.]*\)|\1\2\3|'`;;
- *)
- int_macosx_version=unexpected;;
- esac]
- test $int_macosx_version != unexpected || {
- AC_MSG_ERROR([Unexpected MacOSX version ($macosx_version) returned by 'sw_vers -productVersion'; this configure script probably needs to be updated])
- }
- AC_TRY_COMPILE([
-#if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ > $int_macosx_version
-#error Compiling for a newer MacOSX version...
-#endif
- ], [;],
- [],
- [AC_MSG_ERROR([
-
- You are natively building Erlang/OTP for a later version of MacOSX
- than current version ($macosx_version). You either need to
- cross-build Erlang/OTP, or set the environment variable
- MACOSX_DEPLOYMENT_TARGET to $macosx_version (or a lower version).
-
-])])
- ;;
- *)
- ;;
- esac
-fi
-
ERL_DED
AC_CONFIG_FILES([../Makefile output.mk ../make/$host/otp_ded.mk:../make/otp_ded.mk.in])
diff --git a/make/otp_patch_solve_forward_merge_version b/make/otp_patch_solve_forward_merge_version
index 60d3b2f4a4..b6a7d89c68 100644
--- a/make/otp_patch_solve_forward_merge_version
+++ b/make/otp_patch_solve_forward_merge_version
@@ -1 +1 @@
-15
+16
diff --git a/make/otp_release_targets.mk b/make/otp_release_targets.mk
index fe1f5103ca..a2ba51c9cf 100644
--- a/make/otp_release_targets.mk
+++ b/make/otp_release_targets.mk
@@ -205,7 +205,12 @@ endif
# Standard release target
# ----------------------------------------------------
-pdf man chunks html: $(XML_GEN_FILES) $(SPECS_FILES) $(TOP_SPECS_FILE)
+ifneq ($(XML_ALL_REF3_FILES),)
+man chunks: $(XML_GEN_FILES) $(SPECS_FILES) $(TOP_SPECS_FILE)
+else
+man chunks:
+endif
+pdf html: $(XML_GEN_FILES) $(SPECS_FILES) $(TOP_SPECS_FILE)
release_man_spec: man
release_pdf_spec: pdf
release_chunks_spec: chunks
diff --git a/make/otp_version_tickets b/make/otp_version_tickets
index 63038d6310..70665d296d 100644
--- a/make/otp_version_tickets
+++ b/make/otp_version_tickets
@@ -1,2 +1,4 @@
OTP-16607
-OTP-17021
+OTP-17185
+OTP-17190
+OTP-17191
diff --git a/make/otp_version_tickets_in_merge b/make/otp_version_tickets_in_merge
index 14d2e5d844..e69de29bb2 100644
--- a/make/otp_version_tickets_in_merge
+++ b/make/otp_version_tickets_in_merge
@@ -1,4 +0,0 @@
-OTP-16607
-OTP-16780
-OTP-16804
-OTP-16818
diff --git a/otp_versions.table b/otp_versions.table
index cf6da5ec1f..6ff8eaaefa 100644
--- a/otp_versions.table
+++ b/otp_versions.table
@@ -1,3 +1,9 @@
+OTP-23.2.5 : erts-11.1.8 ssl-10.2.3 tools-3.4.3 # asn1-5.0.14 common_test-1.19.1 compiler-7.6.6 crypto-4.8.3 debugger-5.0 dialyzer-4.3 diameter-2.2.3 edoc-0.12 eldap-1.2.8 erl_docgen-1.0.2 erl_interface-4.0.2 et-1.6.4 eunit-2.6 ftp-1.0.5 hipe-4.0.1 inets-7.3.1 jinterface-1.11 kernel-7.2 megaco-3.19.5 mnesia-4.18.1 observer-2.9.5 odbc-2.13.2 os_mon-2.6.1 parsetools-2.2 public_key-1.9.2 reltool-0.8 runtime_tools-1.15.1 sasl-4.0.1 snmp-5.7.3 ssh-4.10.7 stdlib-3.14 syntax_tools-2.4 tftp-1.0.2 wx-1.9.2 xmerl-1.3.26 :
+OTP-23.2.4 : snmp-5.7.3 ssl-10.2.2 # asn1-5.0.14 common_test-1.19.1 compiler-7.6.6 crypto-4.8.3 debugger-5.0 dialyzer-4.3 diameter-2.2.3 edoc-0.12 eldap-1.2.8 erl_docgen-1.0.2 erl_interface-4.0.2 erts-11.1.7 et-1.6.4 eunit-2.6 ftp-1.0.5 hipe-4.0.1 inets-7.3.1 jinterface-1.11 kernel-7.2 megaco-3.19.5 mnesia-4.18.1 observer-2.9.5 odbc-2.13.2 os_mon-2.6.1 parsetools-2.2 public_key-1.9.2 reltool-0.8 runtime_tools-1.15.1 sasl-4.0.1 ssh-4.10.7 stdlib-3.14 syntax_tools-2.4 tftp-1.0.2 tools-3.4.2 wx-1.9.2 xmerl-1.3.26 :
+OTP-23.2.3 : crypto-4.8.3 erts-11.1.7 snmp-5.7.2 ssh-4.10.7 # asn1-5.0.14 common_test-1.19.1 compiler-7.6.6 debugger-5.0 dialyzer-4.3 diameter-2.2.3 edoc-0.12 eldap-1.2.8 erl_docgen-1.0.2 erl_interface-4.0.2 et-1.6.4 eunit-2.6 ftp-1.0.5 hipe-4.0.1 inets-7.3.1 jinterface-1.11 kernel-7.2 megaco-3.19.5 mnesia-4.18.1 observer-2.9.5 odbc-2.13.2 os_mon-2.6.1 parsetools-2.2 public_key-1.9.2 reltool-0.8 runtime_tools-1.15.1 sasl-4.0.1 ssl-10.2.1 stdlib-3.14 syntax_tools-2.4 tftp-1.0.2 tools-3.4.2 wx-1.9.2 xmerl-1.3.26 :
+OTP-23.2.2 : crypto-4.8.2 erl_interface-4.0.2 erts-11.1.6 megaco-3.19.5 odbc-2.13.2 snmp-5.7.1 ssl-10.2.1 # asn1-5.0.14 common_test-1.19.1 compiler-7.6.6 debugger-5.0 dialyzer-4.3 diameter-2.2.3 edoc-0.12 eldap-1.2.8 erl_docgen-1.0.2 et-1.6.4 eunit-2.6 ftp-1.0.5 hipe-4.0.1 inets-7.3.1 jinterface-1.11 kernel-7.2 mnesia-4.18.1 observer-2.9.5 os_mon-2.6.1 parsetools-2.2 public_key-1.9.2 reltool-0.8 runtime_tools-1.15.1 sasl-4.0.1 ssh-4.10.6 stdlib-3.14 syntax_tools-2.4 tftp-1.0.2 tools-3.4.2 wx-1.9.2 xmerl-1.3.26 :
+OTP-23.2.1 : erts-11.1.5 # asn1-5.0.14 common_test-1.19.1 compiler-7.6.6 crypto-4.8.1 debugger-5.0 dialyzer-4.3 diameter-2.2.3 edoc-0.12 eldap-1.2.8 erl_docgen-1.0.2 erl_interface-4.0.1 et-1.6.4 eunit-2.6 ftp-1.0.5 hipe-4.0.1 inets-7.3.1 jinterface-1.11 kernel-7.2 megaco-3.19.4 mnesia-4.18.1 observer-2.9.5 odbc-2.13.1 os_mon-2.6.1 parsetools-2.2 public_key-1.9.2 reltool-0.8 runtime_tools-1.15.1 sasl-4.0.1 snmp-5.7 ssh-4.10.6 ssl-10.2 stdlib-3.14 syntax_tools-2.4 tftp-1.0.2 tools-3.4.2 wx-1.9.2 xmerl-1.3.26 :
+OTP-23.2 : common_test-1.19.1 compiler-7.6.6 crypto-4.8.1 dialyzer-4.3 erl_docgen-1.0.2 erts-11.1.4 inets-7.3.1 kernel-7.2 megaco-3.19.4 mnesia-4.18.1 public_key-1.9.2 snmp-5.7 ssh-4.10.6 ssl-10.2 stdlib-3.14 syntax_tools-2.4 tools-3.4.2 wx-1.9.2 xmerl-1.3.26 # asn1-5.0.14 debugger-5.0 diameter-2.2.3 edoc-0.12 eldap-1.2.8 erl_interface-4.0.1 et-1.6.4 eunit-2.6 ftp-1.0.5 hipe-4.0.1 jinterface-1.11 observer-2.9.5 odbc-2.13.1 os_mon-2.6.1 parsetools-2.2 reltool-0.8 runtime_tools-1.15.1 sasl-4.0.1 tftp-1.0.2 :
OTP-23.1.5 : ssh-4.10.5 # asn1-5.0.14 common_test-1.19 compiler-7.6.5 crypto-4.8 debugger-5.0 dialyzer-4.2.1 diameter-2.2.3 edoc-0.12 eldap-1.2.8 erl_docgen-1.0.1 erl_interface-4.0.1 erts-11.1.3 et-1.6.4 eunit-2.6 ftp-1.0.5 hipe-4.0.1 inets-7.3 jinterface-1.11 kernel-7.1 megaco-3.19.3 mnesia-4.18 observer-2.9.5 odbc-2.13.1 os_mon-2.6.1 parsetools-2.2 public_key-1.9.1 reltool-0.8 runtime_tools-1.15.1 sasl-4.0.1 snmp-5.6.1 ssl-10.1 stdlib-3.13.2 syntax_tools-2.3.1 tftp-1.0.2 tools-3.4.1 wx-1.9.1 xmerl-1.3.25 :
OTP-23.1.4 : ssh-4.10.4 # asn1-5.0.14 common_test-1.19 compiler-7.6.5 crypto-4.8 debugger-5.0 dialyzer-4.2.1 diameter-2.2.3 edoc-0.12 eldap-1.2.8 erl_docgen-1.0.1 erl_interface-4.0.1 erts-11.1.3 et-1.6.4 eunit-2.6 ftp-1.0.5 hipe-4.0.1 inets-7.3 jinterface-1.11 kernel-7.1 megaco-3.19.3 mnesia-4.18 observer-2.9.5 odbc-2.13.1 os_mon-2.6.1 parsetools-2.2 public_key-1.9.1 reltool-0.8 runtime_tools-1.15.1 sasl-4.0.1 snmp-5.6.1 ssl-10.1 stdlib-3.13.2 syntax_tools-2.3.1 tftp-1.0.2 tools-3.4.1 wx-1.9.1 xmerl-1.3.25 :
OTP-23.1.3 : erts-11.1.3 ssh-4.10.3 # asn1-5.0.14 common_test-1.19 compiler-7.6.5 crypto-4.8 debugger-5.0 dialyzer-4.2.1 diameter-2.2.3 edoc-0.12 eldap-1.2.8 erl_docgen-1.0.1 erl_interface-4.0.1 et-1.6.4 eunit-2.6 ftp-1.0.5 hipe-4.0.1 inets-7.3 jinterface-1.11 kernel-7.1 megaco-3.19.3 mnesia-4.18 observer-2.9.5 odbc-2.13.1 os_mon-2.6.1 parsetools-2.2 public_key-1.9.1 reltool-0.8 runtime_tools-1.15.1 sasl-4.0.1 snmp-5.6.1 ssl-10.1 stdlib-3.13.2 syntax_tools-2.3.1 tftp-1.0.2 tools-3.4.1 wx-1.9.1 xmerl-1.3.25 :
@@ -9,6 +15,9 @@ OTP-23.0.3 : compiler-7.6.2 erts-11.0.3 # asn1-5.0.13 common_test-1.19 crypto-4.
OTP-23.0.2 : erts-11.0.2 megaco-3.19.1 # asn1-5.0.13 common_test-1.19 compiler-7.6.1 crypto-4.7 debugger-5.0 dialyzer-4.2 diameter-2.2.3 edoc-0.12 eldap-1.2.8 erl_docgen-1.0 erl_interface-4.0 et-1.6.4 eunit-2.5 ftp-1.0.4 hipe-4.0 inets-7.2 jinterface-1.11 kernel-7.0 mnesia-4.17 observer-2.9.4 odbc-2.13 os_mon-2.5.2 parsetools-2.2 public_key-1.8 reltool-0.8 runtime_tools-1.15 sasl-4.0 snmp-5.6 ssh-4.10 ssl-10.0 stdlib-3.13 syntax_tools-2.3 tftp-1.0.2 tools-3.4 wx-1.9.1 xmerl-1.3.25 :
OTP-23.0.1 : compiler-7.6.1 erts-11.0.1 # asn1-5.0.13 common_test-1.19 crypto-4.7 debugger-5.0 dialyzer-4.2 diameter-2.2.3 edoc-0.12 eldap-1.2.8 erl_docgen-1.0 erl_interface-4.0 et-1.6.4 eunit-2.5 ftp-1.0.4 hipe-4.0 inets-7.2 jinterface-1.11 kernel-7.0 megaco-3.19 mnesia-4.17 observer-2.9.4 odbc-2.13 os_mon-2.5.2 parsetools-2.2 public_key-1.8 reltool-0.8 runtime_tools-1.15 sasl-4.0 snmp-5.6 ssh-4.10 ssl-10.0 stdlib-3.13 syntax_tools-2.3 tftp-1.0.2 tools-3.4 wx-1.9.1 xmerl-1.3.25 :
OTP-23.0 : asn1-5.0.13 common_test-1.19 compiler-7.6 crypto-4.7 debugger-5.0 dialyzer-4.2 edoc-0.12 erl_docgen-1.0 erl_interface-4.0 erts-11.0 eunit-2.5 hipe-4.0 inets-7.2 jinterface-1.11 kernel-7.0 megaco-3.19 mnesia-4.17 observer-2.9.4 odbc-2.13 os_mon-2.5.2 parsetools-2.2 public_key-1.8 runtime_tools-1.15 sasl-4.0 snmp-5.6 ssh-4.10 ssl-10.0 stdlib-3.13 syntax_tools-2.3 tools-3.4 wx-1.9.1 xmerl-1.3.25 # diameter-2.2.3 eldap-1.2.8 et-1.6.4 ftp-1.0.4 reltool-0.8 tftp-1.0.2 :
+OTP-22.3.4.13 : compiler-7.5.4.2 erts-10.7.2.6 megaco-3.18.8.3 snmp-5.5.0.4 # asn1-5.0.12 common_test-1.18.2 crypto-4.6.5.1 debugger-4.2.8 dialyzer-4.1.1 diameter-2.2.3 edoc-0.11 eldap-1.2.8 erl_docgen-0.11 erl_interface-3.13.2 et-1.6.4 eunit-2.4.1 ftp-1.0.4.1 hipe-3.19.3 inets-7.1.3.3 jinterface-1.10.1 kernel-6.5.2.1 mnesia-4.16.3.1 observer-2.9.3 odbc-2.12.4 os_mon-2.5.1.1 parsetools-2.1.8 public_key-1.7.2 reltool-0.8 runtime_tools-1.14 sasl-3.4.2 ssh-4.9.1.2 ssl-9.6.2.3 stdlib-3.12.1 syntax_tools-2.2.1 tftp-1.0.2 tools-3.3.1 wx-1.9 xmerl-1.3.24 :
+OTP-22.3.4.12 : erts-10.7.2.5 ssl-9.6.2.3 # asn1-5.0.12 common_test-1.18.2 compiler-7.5.4.1 crypto-4.6.5.1 debugger-4.2.8 dialyzer-4.1.1 diameter-2.2.3 edoc-0.11 eldap-1.2.8 erl_docgen-0.11 erl_interface-3.13.2 et-1.6.4 eunit-2.4.1 ftp-1.0.4.1 hipe-3.19.3 inets-7.1.3.3 jinterface-1.10.1 kernel-6.5.2.1 megaco-3.18.8.2 mnesia-4.16.3.1 observer-2.9.3 odbc-2.12.4 os_mon-2.5.1.1 parsetools-2.1.8 public_key-1.7.2 reltool-0.8 runtime_tools-1.14 sasl-3.4.2 snmp-5.5.0.3 ssh-4.9.1.2 stdlib-3.12.1 syntax_tools-2.2.1 tftp-1.0.2 tools-3.3.1 wx-1.9 xmerl-1.3.24 :
+OTP-22.3.4.11 : erts-10.7.2.4 mnesia-4.16.3.1 os_mon-2.5.1.1 ssh-4.9.1.2 # asn1-5.0.12 common_test-1.18.2 compiler-7.5.4.1 crypto-4.6.5.1 debugger-4.2.8 dialyzer-4.1.1 diameter-2.2.3 edoc-0.11 eldap-1.2.8 erl_docgen-0.11 erl_interface-3.13.2 et-1.6.4 eunit-2.4.1 ftp-1.0.4.1 hipe-3.19.3 inets-7.1.3.3 jinterface-1.10.1 kernel-6.5.2.1 megaco-3.18.8.2 observer-2.9.3 odbc-2.12.4 parsetools-2.1.8 public_key-1.7.2 reltool-0.8 runtime_tools-1.14 sasl-3.4.2 snmp-5.5.0.3 ssl-9.6.2.2 stdlib-3.12.1 syntax_tools-2.2.1 tftp-1.0.2 tools-3.3.1 wx-1.9 xmerl-1.3.24 :
OTP-22.3.4.10 : megaco-3.18.8.2 # asn1-5.0.12 common_test-1.18.2 compiler-7.5.4.1 crypto-4.6.5.1 debugger-4.2.8 dialyzer-4.1.1 diameter-2.2.3 edoc-0.11 eldap-1.2.8 erl_docgen-0.11 erl_interface-3.13.2 erts-10.7.2.3 et-1.6.4 eunit-2.4.1 ftp-1.0.4.1 hipe-3.19.3 inets-7.1.3.3 jinterface-1.10.1 kernel-6.5.2.1 mnesia-4.16.3 observer-2.9.3 odbc-2.12.4 os_mon-2.5.1 parsetools-2.1.8 public_key-1.7.2 reltool-0.8 runtime_tools-1.14 sasl-3.4.2 snmp-5.5.0.3 ssh-4.9.1.1 ssl-9.6.2.2 stdlib-3.12.1 syntax_tools-2.2.1 tftp-1.0.2 tools-3.3.1 wx-1.9 xmerl-1.3.24 :
OTP-22.3.4.9 : ssh-4.9.1.1 # asn1-5.0.12 common_test-1.18.2 compiler-7.5.4.1 crypto-4.6.5.1 debugger-4.2.8 dialyzer-4.1.1 diameter-2.2.3 edoc-0.11 eldap-1.2.8 erl_docgen-0.11 erl_interface-3.13.2 erts-10.7.2.3 et-1.6.4 eunit-2.4.1 ftp-1.0.4.1 hipe-3.19.3 inets-7.1.3.3 jinterface-1.10.1 kernel-6.5.2.1 megaco-3.18.8.1 mnesia-4.16.3 observer-2.9.3 odbc-2.12.4 os_mon-2.5.1 parsetools-2.1.8 public_key-1.7.2 reltool-0.8 runtime_tools-1.14 sasl-3.4.2 snmp-5.5.0.3 ssl-9.6.2.2 stdlib-3.12.1 syntax_tools-2.2.1 tftp-1.0.2 tools-3.3.1 wx-1.9 xmerl-1.3.24 :
OTP-22.3.4.8 : snmp-5.5.0.3 # asn1-5.0.12 common_test-1.18.2 compiler-7.5.4.1 crypto-4.6.5.1 debugger-4.2.8 dialyzer-4.1.1 diameter-2.2.3 edoc-0.11 eldap-1.2.8 erl_docgen-0.11 erl_interface-3.13.2 erts-10.7.2.3 et-1.6.4 eunit-2.4.1 ftp-1.0.4.1 hipe-3.19.3 inets-7.1.3.3 jinterface-1.10.1 kernel-6.5.2.1 megaco-3.18.8.1 mnesia-4.16.3 observer-2.9.3 odbc-2.12.4 os_mon-2.5.1 parsetools-2.1.8 public_key-1.7.2 reltool-0.8 runtime_tools-1.14 sasl-3.4.2 ssh-4.9.1 ssl-9.6.2.2 stdlib-3.12.1 syntax_tools-2.2.1 tftp-1.0.2 tools-3.3.1 wx-1.9 xmerl-1.3.24 :
@@ -51,6 +60,7 @@ OTP-22.0.3 : compiler-7.4.2 dialyzer-4.0.1 erts-10.4.2 ssl-9.3.2 stdlib-3.9.2 #
OTP-22.0.2 : compiler-7.4.1 crypto-4.5.1 erts-10.4.1 stdlib-3.9.1 # asn1-5.0.9 common_test-1.17.3 debugger-4.2.7 dialyzer-4.0 diameter-2.2.1 edoc-0.11 eldap-1.2.8 erl_docgen-0.9.1 erl_interface-3.12 et-1.6.4 eunit-2.3.7 ftp-1.0.2 hipe-3.19 inets-7.0.8 jinterface-1.10 kernel-6.4 megaco-3.18.5 mnesia-4.16 observer-2.9.1 odbc-2.12.4 os_mon-2.5 parsetools-2.1.8 public_key-1.6.7 reltool-0.8 runtime_tools-1.13.3 sasl-3.4 snmp-5.3 ssh-4.7.7 ssl-9.3.1 syntax_tools-2.2 tftp-1.0.1 tools-3.2 wx-1.8.8 xmerl-1.3.21 :
OTP-22.0.1 : ssl-9.3.1 # asn1-5.0.9 common_test-1.17.3 compiler-7.4 crypto-4.5 debugger-4.2.7 dialyzer-4.0 diameter-2.2.1 edoc-0.11 eldap-1.2.8 erl_docgen-0.9.1 erl_interface-3.12 erts-10.4 et-1.6.4 eunit-2.3.7 ftp-1.0.2 hipe-3.19 inets-7.0.8 jinterface-1.10 kernel-6.4 megaco-3.18.5 mnesia-4.16 observer-2.9.1 odbc-2.12.4 os_mon-2.5 parsetools-2.1.8 public_key-1.6.7 reltool-0.8 runtime_tools-1.13.3 sasl-3.4 snmp-5.3 ssh-4.7.7 stdlib-3.9 syntax_tools-2.2 tftp-1.0.1 tools-3.2 wx-1.8.8 xmerl-1.3.21 :
OTP-22.0 : asn1-5.0.9 common_test-1.17.3 compiler-7.4 crypto-4.5 debugger-4.2.7 dialyzer-4.0 edoc-0.11 eldap-1.2.8 erl_docgen-0.9.1 erl_interface-3.12 erts-10.4 hipe-3.19 inets-7.0.8 jinterface-1.10 kernel-6.4 megaco-3.18.5 mnesia-4.16 observer-2.9.1 odbc-2.12.4 os_mon-2.5 public_key-1.6.7 reltool-0.8 runtime_tools-1.13.3 sasl-3.4 snmp-5.3 ssh-4.7.7 ssl-9.3 stdlib-3.9 syntax_tools-2.2 tools-3.2 wx-1.8.8 xmerl-1.3.21 # diameter-2.2.1 et-1.6.4 eunit-2.3.7 ftp-1.0.2 parsetools-2.1.8 tftp-1.0.1 :
+OTP-21.3.8.18 : erts-10.3.5.14 ssh-4.7.6.5 # asn1-5.0.8 common_test-1.17.2.1 compiler-7.3.2 crypto-4.4.2.2 debugger-4.2.6 dialyzer-3.3.2 diameter-2.2.1 edoc-0.10 eldap-1.2.7 erl_docgen-0.9 erl_interface-3.11.3 et-1.6.4 eunit-2.3.7 ftp-1.0.2.2 hipe-3.18.3 inets-7.0.7.2 jinterface-1.9.1 kernel-6.3.1.3 megaco-3.18.4 mnesia-4.15.6 observer-2.9 odbc-2.12.3 os_mon-2.4.7 otp_mibs-1.2.1 parsetools-2.1.8 public_key-1.6.6.1 reltool-0.7.8 runtime_tools-1.13.2 sasl-3.3 snmp-5.2.12 ssl-9.2.3.7 stdlib-3.8.2.4 syntax_tools-2.1.7.1 tftp-1.0.1 tools-3.1.0.1 wx-1.8.7 xmerl-1.3.20.1 :
OTP-21.3.8.17 : erts-10.3.5.13 ssl-9.2.3.7 # asn1-5.0.8 common_test-1.17.2.1 compiler-7.3.2 crypto-4.4.2.2 debugger-4.2.6 dialyzer-3.3.2 diameter-2.2.1 edoc-0.10 eldap-1.2.7 erl_docgen-0.9 erl_interface-3.11.3 et-1.6.4 eunit-2.3.7 ftp-1.0.2.2 hipe-3.18.3 inets-7.0.7.2 jinterface-1.9.1 kernel-6.3.1.3 megaco-3.18.4 mnesia-4.15.6 observer-2.9 odbc-2.12.3 os_mon-2.4.7 otp_mibs-1.2.1 parsetools-2.1.8 public_key-1.6.6.1 reltool-0.7.8 runtime_tools-1.13.2 sasl-3.3 snmp-5.2.12 ssh-4.7.6.4 stdlib-3.8.2.4 syntax_tools-2.1.7.1 tftp-1.0.1 tools-3.1.0.1 wx-1.8.7 xmerl-1.3.20.1 :
OTP-21.3.8.16 : erts-10.3.5.12 # asn1-5.0.8 common_test-1.17.2.1 compiler-7.3.2 crypto-4.4.2.2 debugger-4.2.6 dialyzer-3.3.2 diameter-2.2.1 edoc-0.10 eldap-1.2.7 erl_docgen-0.9 erl_interface-3.11.3 et-1.6.4 eunit-2.3.7 ftp-1.0.2.2 hipe-3.18.3 inets-7.0.7.2 jinterface-1.9.1 kernel-6.3.1.3 megaco-3.18.4 mnesia-4.15.6 observer-2.9 odbc-2.12.3 os_mon-2.4.7 otp_mibs-1.2.1 parsetools-2.1.8 public_key-1.6.6.1 reltool-0.7.8 runtime_tools-1.13.2 sasl-3.3 snmp-5.2.12 ssh-4.7.6.4 ssl-9.2.3.6 stdlib-3.8.2.4 syntax_tools-2.1.7.1 tftp-1.0.1 tools-3.1.0.1 wx-1.8.7 xmerl-1.3.20.1 :
OTP-21.3.8.15 : erts-10.3.5.11 ssh-4.7.6.4 ssl-9.2.3.6 stdlib-3.8.2.4 # asn1-5.0.8 common_test-1.17.2.1 compiler-7.3.2 crypto-4.4.2.2 debugger-4.2.6 dialyzer-3.3.2 diameter-2.2.1 edoc-0.10 eldap-1.2.7 erl_docgen-0.9 erl_interface-3.11.3 et-1.6.4 eunit-2.3.7 ftp-1.0.2.2 hipe-3.18.3 inets-7.0.7.2 jinterface-1.9.1 kernel-6.3.1.3 megaco-3.18.4 mnesia-4.15.6 observer-2.9 odbc-2.12.3 os_mon-2.4.7 otp_mibs-1.2.1 parsetools-2.1.8 public_key-1.6.6.1 reltool-0.7.8 runtime_tools-1.13.2 sasl-3.3 snmp-5.2.12 syntax_tools-2.1.7.1 tftp-1.0.1 tools-3.1.0.1 wx-1.8.7 xmerl-1.3.20.1 :
diff --git a/scripts/otp_html_check b/scripts/otp_html_check
index 62d5b47edd..d9c49ef30a 100755
--- a/scripts/otp_html_check
+++ b/scripts/otp_html_check
@@ -409,6 +409,10 @@ if (keys %anchor_refs) {
}
}
+if (keys %missing || keys %anchor_refs) {
+ exit 1;
+}
+
###########################################################################
diff --git a/system/doc/general_info/DEPRECATIONS b/system/doc/general_info/DEPRECATIONS
index 9983df9421..0f212348cf 100644
--- a/system/doc/general_info/DEPRECATIONS
+++ b/system/doc/general_info/DEPRECATIONS
@@ -17,6 +17,15 @@
# is scheduled to be removed in OTP 25.
#
+#
+# Added in OTP 23.2.
+#
+igor:_/_ since=23 remove=24
+erl_tidy:dir/0 since=23 remove=24
+erl_tidy:dir/1 since=23 remove=24
+erl_tidy:file/1 since=23 remove=24
+erl_tidy:module/1 since=23 remove=24
+erl_tidy:module/2 since=23 remove=24
#
# Added in OTP 23.
diff --git a/system/doc/reference_manual/errors.xml b/system/doc/reference_manual/errors.xml
index f53989489a..ab00c1a2ce 100644
--- a/system/doc/reference_manual/errors.xml
+++ b/system/doc/reference_manual/errors.xml
@@ -108,7 +108,7 @@
(see <seeguide marker="#exit_reasons">Exit Reason</seeguide>),
and a stack trace (which aids in finding the code location of
the exception).</p>
- <p>The stack trace can be be bound to a variable from within
+ <p>The stack trace can be bound to a variable from within
a <c>try</c> expression, and is returned for
exceptions of class <c>error</c> from a <c>catch</c> expression.</p>
<p>An exception of class <c>error</c> is also known as a run-time