summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/asn1/doc/src/notes.xml51
-rw-r--r--lib/asn1/src/asn1ct_gen.erl4
-rw-r--r--lib/asn1/test/asn1_SUITE.erl392
-rw-r--r--lib/asn1/test/asn1_test_lib.erl7
-rw-r--r--lib/asn1/test/testEnumExt.erl2
-rw-r--r--lib/asn1/test/testMegaco.erl4
-rw-r--r--lib/asn1/vsn.mk2
-rw-r--r--lib/common_test/doc/src/notes.xml38
-rw-r--r--lib/common_test/vsn.mk2
-rw-r--r--lib/compiler/doc/src/notes.xml123
-rw-r--r--lib/compiler/src/beam_ssa_bool.erl78
-rw-r--r--lib/compiler/src/beam_ssa_opt.erl446
-rw-r--r--lib/compiler/src/beam_trim.erl112
-rw-r--r--lib/compiler/src/compiler.app.src2
-rw-r--r--lib/compiler/test/beam_ssa_SUITE.erl76
-rw-r--r--lib/compiler/test/guard_SUITE.erl166
-rw-r--r--lib/compiler/vsn.mk2
-rw-r--r--lib/crypto/doc/src/notes.xml87
-rw-r--r--lib/crypto/test/crypto_SUITE.erl482
-rw-r--r--lib/crypto/test/crypto_property_test_SUITE.erl1
-rw-r--r--lib/crypto/vsn.mk2
-rw-r--r--lib/debugger/doc/src/notes.xml37
-rw-r--r--lib/debugger/vsn.mk2
-rw-r--r--lib/dialyzer/doc/src/notes.xml14
-rw-r--r--lib/dialyzer/vsn.mk2
-rw-r--r--lib/edoc/doc/src/notes.xml26
-rw-r--r--lib/edoc/src/edoc_data.erl36
-rw-r--r--lib/edoc/src/edoc_extract.erl2
-rw-r--r--lib/edoc/src/edoc_layout.erl49
-rw-r--r--lib/edoc/src/edoc_specs.erl30
-rw-r--r--lib/edoc/vsn.mk2
-rw-r--r--lib/erl_docgen/Makefile2
-rw-r--r--lib/erl_docgen/doc/src/doc_storage.xml86
-rw-r--r--lib/erl_docgen/doc/src/notes.xml48
-rw-r--r--lib/erl_docgen/priv/bin/specs_gen.escript3
-rwxr-xr-xlib/erl_docgen/priv/bin/validate_links.escript5
-rw-r--r--lib/erl_docgen/priv/dtd/common.dtd2
-rw-r--r--lib/erl_docgen/priv/xsl/db_html.xsl4
-rw-r--r--lib/erl_docgen/src/docgen_edoc_xml_cb.erl49
-rw-r--r--lib/erl_docgen/src/docgen_xml_to_chunk.erl142
-rw-r--r--lib/erl_docgen/vsn.mk2
-rw-r--r--lib/erl_interface/doc/src/ei.xml437
-rw-r--r--lib/erl_interface/doc/src/ei_connect.xml303
-rw-r--r--lib/erl_interface/doc/src/ei_global.xml19
-rw-r--r--lib/erl_interface/doc/src/ei_users_guide.xml12
-rw-r--r--lib/erl_interface/doc/src/erl_call_cmd.xml7
-rw-r--r--lib/erl_interface/doc/src/notes.xml172
-rw-r--r--lib/erl_interface/doc/src/ref_man.xml2
-rw-r--r--lib/erl_interface/doc/src/registry.xml5
-rw-r--r--lib/erl_interface/include/ei.h48
-rw-r--r--lib/erl_interface/src/Makefile.in6
-rw-r--r--lib/erl_interface/src/connect/ei_connect.c380
-rw-r--r--lib/erl_interface/src/connect/eirecv.c48
-rw-r--r--lib/erl_interface/src/connect/send.c37
-rw-r--r--lib/erl_interface/src/connect/send_exit.c50
-rw-r--r--lib/erl_interface/src/connect/send_reg.c43
-rw-r--r--lib/erl_interface/src/decode/decode_iodata.c166
-rw-r--r--lib/erl_interface/src/decode/decode_ref.c3
-rw-r--r--lib/erl_interface/src/encode/encode_trace.c18
-rw-r--r--lib/erl_interface/src/global/global_names.c38
-rw-r--r--lib/erl_interface/src/global/global_register.c60
-rw-r--r--lib/erl_interface/src/global/global_unregister.c54
-rw-r--r--lib/erl_interface/src/global/global_whereis.c42
-rw-r--r--lib/erl_interface/src/misc/ei_cmp_nc.c117
-rw-r--r--lib/erl_interface/src/misc/ei_decode_term.c5
-rw-r--r--lib/erl_interface/src/misc/ei_portio.h8
-rw-r--r--lib/erl_interface/src/misc/show_msg.c6
-rw-r--r--lib/erl_interface/src/prog/erl_call.c111
-rw-r--r--lib/erl_interface/src/prog/erl_start.c24
-rw-r--r--lib/erl_interface/src/registry/reg_dump.c22
-rw-r--r--lib/erl_interface/src/registry/reg_restore.c20
-rw-r--r--lib/erl_interface/test/Makefile8
-rw-r--r--lib/erl_interface/test/ei_connect_SUITE.erl110
-rw-r--r--lib/erl_interface/test/ei_connect_SUITE_data/ei_connect_test.c78
-rw-r--r--lib/erl_interface/test/ei_decode_SUITE.erl161
-rw-r--r--lib/erl_interface/test/ei_decode_SUITE_data/ei_decode_test.c187
-rw-r--r--lib/erl_interface/test/ei_global_SUITE_data/ei_global_test.c1
-rw-r--r--lib/erl_interface/test/erl_call_SUITE.erl42
-rw-r--r--lib/erl_interface/vsn.mk2
-rw-r--r--lib/eunit/doc/src/notes.xml29
-rw-r--r--lib/eunit/vsn.mk2
-rw-r--r--lib/hipe/doc/src/notes.xml32
-rw-r--r--lib/hipe/vsn.mk2
-rw-r--r--lib/inets/doc/src/notes.xml26
-rw-r--r--lib/inets/vsn.mk2
-rw-r--r--lib/jinterface/doc/src/notes.xml29
-rw-r--r--lib/jinterface/vsn.mk2
-rw-r--r--lib/kernel/doc/src/Makefile8
-rw-r--r--lib/kernel/doc/src/code.xml13
-rw-r--r--lib/kernel/doc/src/eep48_chapter.xml188
-rw-r--r--lib/kernel/doc/src/erl_epmd.xml2
-rw-r--r--lib/kernel/doc/src/erpc.xml40
-rw-r--r--lib/kernel/doc/src/gen_tcp.xml2
-rw-r--r--lib/kernel/doc/src/kernel_app.xml8
-rw-r--r--lib/kernel/doc/src/notes.xml400
-rw-r--r--lib/kernel/doc/src/part.xml2
-rw-r--r--lib/kernel/doc/src/ref_man.xml1
-rw-r--r--lib/kernel/doc/src/socket.xml1204
-rw-r--r--lib/kernel/doc/src/socket_usage.xml913
-rw-r--r--lib/kernel/doc/src/specs.xml1
-rw-r--r--lib/kernel/src/Makefile1
-rw-r--r--lib/kernel/src/application_controller.erl13
-rw-r--r--lib/kernel/src/code.erl30
-rw-r--r--lib/kernel/src/dist_util.erl11
-rw-r--r--lib/kernel/src/gen_tcp.erl2
-rw-r--r--lib/kernel/src/gen_tcp_socket.erl60
-rw-r--r--lib/kernel/src/inet.erl5
-rw-r--r--lib/kernel/src/inet6_udp.erl6
-rw-r--r--lib/kernel/src/inet_udp.erl6
-rw-r--r--lib/kernel/src/kernel.app.src3
-rw-r--r--lib/kernel/src/kernel.appup.src7
-rw-r--r--lib/kernel/src/net.erl47
-rw-r--r--lib/kernel/src/net_kernel.erl24
-rw-r--r--lib/kernel/src/socket.erl2570
-rw-r--r--lib/kernel/src/user_drv.erl7
-rw-r--r--lib/kernel/test/Makefile24
-rw-r--r--lib/kernel/test/erl_boot_server_SUITE.erl14
-rw-r--r--lib/kernel/test/file_name_SUITE.erl4
-rw-r--r--lib/kernel/test/gen_sctp_SUITE.erl152
-rw-r--r--lib/kernel/test/gen_tcp_echo_SUITE.erl10
-rw-r--r--lib/kernel/test/gen_udp_SUITE.erl84
-rw-r--r--lib/kernel/test/global_SUITE.erl34
-rw-r--r--lib/kernel/test/init_SUITE.erl11
-rw-r--r--lib/kernel/test/logger_std_h_SUITE.erl13
-rw-r--r--lib/kernel/test/net_SUITE.erl19
-rw-r--r--lib/kernel/test/socket_SUITE.erl44297
-rw-r--r--lib/kernel/test/socket_test_evaluator.erl664
-rw-r--r--lib/kernel/test/socket_test_evaluator.hrl68
-rw-r--r--lib/kernel/test/socket_test_lib.erl303
-rw-r--r--lib/kernel/test/socket_test_logger.erl118
-rw-r--r--lib/kernel/test/socket_test_ttest.hrl32
-rw-r--r--lib/kernel/test/socket_test_ttest_client.hrl141
-rw-r--r--lib/kernel/test/socket_test_ttest_lib.erl127
-rw-r--r--lib/kernel/test/socket_test_ttest_tcp_client.erl687
-rw-r--r--lib/kernel/test/socket_test_ttest_tcp_client_gen.erl49
-rw-r--r--lib/kernel/test/socket_test_ttest_tcp_client_socket.erl116
-rw-r--r--lib/kernel/test/socket_test_ttest_tcp_gen.erl137
-rw-r--r--lib/kernel/test/socket_test_ttest_tcp_server.erl684
-rw-r--r--lib/kernel/test/socket_test_ttest_tcp_server_gen.erl39
-rw-r--r--lib/kernel/test/socket_test_ttest_tcp_server_socket.erl46
-rw-r--r--lib/kernel/test/socket_test_ttest_tcp_socket.erl724
-rw-r--r--lib/kernel/vsn.mk2
-rw-r--r--lib/megaco/doc/src/notes.xml42
-rw-r--r--lib/megaco/src/text/megaco_text_mini_parser.yrl8
-rw-r--r--lib/megaco/test/megaco_codec_mini_SUITE.erl401
-rw-r--r--lib/megaco/test/megaco_mess_SUITE.erl35
-rw-r--r--lib/megaco/test/megaco_segment_SUITE.erl239
-rw-r--r--lib/megaco/test/megaco_test_lib.erl841
-rw-r--r--lib/megaco/test/megaco_test_lib.hrl2
-rw-r--r--lib/megaco/test/megaco_udp_SUITE.erl134
-rw-r--r--lib/megaco/vsn.mk2
-rw-r--r--lib/mnesia/doc/specs/.gitignore1
-rw-r--r--lib/mnesia/doc/src/Makefile3
-rw-r--r--lib/mnesia/doc/src/Mnesia_chap2.xmlsrc2
-rw-r--r--lib/mnesia/doc/src/Mnesia_chap4.xmlsrc16
-rw-r--r--lib/mnesia/doc/src/mnesia.xml346
-rw-r--r--lib/mnesia/doc/src/notes.xml46
-rw-r--r--lib/mnesia/doc/src/specs.xml6
-rw-r--r--lib/mnesia/src/mnesia.erl67
-rw-r--r--lib/mnesia/src/mnesia_recover.erl8
-rw-r--r--lib/mnesia/vsn.mk2
-rw-r--r--lib/observer/doc/src/notes.xml15
-rw-r--r--lib/observer/src/observer.app.src4
-rw-r--r--lib/observer/src/observer_alloc_wx.erl9
-rw-r--r--lib/observer/vsn.mk2
-rw-r--r--lib/odbc/c_src/odbcserver.c2
-rw-r--r--lib/odbc/c_src/odbcserver.h2
-rw-r--r--lib/odbc/doc/src/notes.xml30
-rw-r--r--lib/odbc/vsn.mk2
-rw-r--r--lib/os_mon/c_src/nteventlog/elog_main.c10
-rw-r--r--lib/os_mon/doc/src/notes.xml27
-rw-r--r--lib/os_mon/vsn.mk2
-rw-r--r--lib/parsetools/doc/src/notes.xml16
-rw-r--r--lib/parsetools/vsn.mk2
-rw-r--r--lib/public_key/asn1/OTP-PKIX.asn118
-rw-r--r--lib/public_key/asn1/PKCS-1.asn1335
-rw-r--r--lib/public_key/doc/src/notes.xml24
-rw-r--r--lib/public_key/doc/src/public_key.xml16
-rw-r--r--lib/public_key/doc/src/public_key_records.xml17
-rw-r--r--lib/public_key/src/pubkey_cert.erl129
-rw-r--r--lib/public_key/src/pubkey_cert_records.erl3
-rw-r--r--lib/public_key/src/public_key.erl100
-rw-r--r--lib/public_key/test/public_key_SUITE.erl119
-rw-r--r--lib/public_key/test/public_key_SUITE_data/rsa_pss_pss_key.pem29
-rw-r--r--lib/public_key/vsn.mk2
-rw-r--r--lib/reltool/test/reltool_test_lib.erl2
-rw-r--r--lib/runtime_tools/doc/src/notes.xml21
-rw-r--r--lib/runtime_tools/src/runtime_tools.app.src4
-rw-r--r--lib/runtime_tools/vsn.mk2
-rw-r--r--lib/sasl/doc/specs/.gitignore1
-rw-r--r--lib/sasl/doc/src/Makefile2
-rw-r--r--lib/sasl/doc/src/notes.xml39
-rw-r--r--lib/sasl/doc/src/specs.xml4
-rw-r--r--lib/sasl/doc/src/systools.xml28
-rw-r--r--lib/sasl/src/sasl.appup.src7
-rw-r--r--lib/sasl/src/systools.erl24
-rw-r--r--lib/sasl/src/systools_make.erl111
-rw-r--r--lib/sasl/test/systools_SUITE.erl83
-rw-r--r--lib/sasl/vsn.mk2
-rw-r--r--lib/snmp/doc/src/notes.xml71
-rw-r--r--lib/snmp/test/snmp_agent_SUITE.erl55
-rw-r--r--lib/snmp/test/snmp_agent_mibs_SUITE.erl155
-rw-r--r--lib/snmp/test/snmp_manager_SUITE.erl1043
-rw-r--r--lib/snmp/test/snmp_manager_user.erl197
-rw-r--r--lib/snmp/test/snmp_manager_user_SUITE.erl33
-rw-r--r--lib/snmp/test/snmp_test_lib.erl873
-rw-r--r--lib/snmp/test/snmp_test_lib.hrl3
-rw-r--r--lib/snmp/test/snmp_test_manager.erl8
-rw-r--r--lib/snmp/test/snmp_to_snmpnet_SUITE.erl4
-rw-r--r--lib/snmp/vsn.mk2
-rw-r--r--lib/ssh/doc/src/notes.xml257
-rw-r--r--lib/ssh/doc/src/ssh.xml40
-rw-r--r--lib/ssh/src/ssh.hrl2
-rw-r--r--lib/ssh/src/ssh_acceptor.erl5
-rw-r--r--lib/ssh/src/ssh_auth.erl361
-rw-r--r--lib/ssh/src/ssh_cli.erl5
-rw-r--r--lib/ssh/src/ssh_client_channel.erl6
-rw-r--r--lib/ssh/src/ssh_connection_handler.erl219
-rw-r--r--lib/ssh/src/ssh_dbg.erl128
-rw-r--r--lib/ssh/src/ssh_message.erl11
-rw-r--r--lib/ssh/src/ssh_options.erl35
-rw-r--r--lib/ssh/src/ssh_sftp.erl4
-rw-r--r--lib/ssh/src/ssh_sftpd.erl4
-rw-r--r--lib/ssh/src/ssh_shell.erl4
-rw-r--r--lib/ssh/src/ssh_transport.erl9
-rw-r--r--lib/ssh/test/.gitignore2
-rw-r--r--lib/ssh/test/Makefile1
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server.erl9
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_dsa21
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_dsa.pub1
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa2565
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa256.pub1
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa3846
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa384.pub1
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa5217
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa521.pub1
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed255197
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed25519.pub1
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed44810
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed448.pub1
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_rsa38
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_rsa.pub1
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key2565
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key256.pub1
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key3846
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key384.pub1
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key5217
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key521.pub1
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed25519_key7
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed25519_key.pub1
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed448_key10
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed448_key.pub1
-rw-r--r--lib/ssh/test/ssh_agent_SUITE.erl38
-rw-r--r--lib/ssh/test/ssh_agent_SUITE_data/ssh_host_dsa_key13
-rw-r--r--lib/ssh/test/ssh_agent_SUITE_data/ssh_host_dsa_key.pub11
-rw-r--r--lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key2565
-rw-r--r--lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key256.pub1
-rw-r--r--lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key3846
-rw-r--r--lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key384.pub1
-rw-r--r--lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key5217
-rw-r--r--lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key521.pub1
-rw-r--r--lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed25519_key7
-rw-r--r--lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed25519_key.pub1
-rw-r--r--lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed448_key10
-rw-r--r--lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed448_key.pub1
-rw-r--r--lib/ssh/test/ssh_agent_SUITE_data/ssh_host_rsa_key.pub5
-rw-r--r--lib/ssh/test/ssh_algorithms_SUITE.erl125
-rw-r--r--lib/ssh/test/ssh_algorithms_SUITE_data/id_dsa20
-rw-r--r--lib/ssh/test/ssh_algorithms_SUITE_data/id_dsa.pub1
-rw-r--r--lib/ssh/test/ssh_algorithms_SUITE_data/id_rsa50
-rw-r--r--lib/ssh/test/ssh_algorithms_SUITE_data/id_rsa.pub1
-rw-r--r--lib/ssh/test/ssh_basic_SUITE.erl830
-rw-r--r--lib/ssh/test/ssh_basic_SUITE_data/id_dsa21
-rw-r--r--lib/ssh/test/ssh_basic_SUITE_data/id_dsa.pub1
-rw-r--r--lib/ssh/test/ssh_basic_SUITE_data/id_rsa38
-rw-r--r--lib/ssh/test/ssh_basic_SUITE_data/id_rsa.pub1
-rw-r--r--lib/ssh/test/ssh_compat_SUITE.erl17
-rw-r--r--lib/ssh/test/ssh_connection_SUITE.erl31
-rw-r--r--lib/ssh/test/ssh_dbg_SUITE.erl9
-rw-r--r--lib/ssh/test/ssh_engine_SUITE.erl21
-rw-r--r--lib/ssh/test/ssh_options_SUITE.erl14
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/id_dsa21
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/id_dsa.pub1
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/id_ecdsa2565
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/id_ecdsa256.pub1
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/id_ecdsa3846
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/id_ecdsa384.pub1
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/id_ecdsa5217
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/id_ecdsa521.pub1
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/id_ed255197
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/id_ed25519.pub1
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/id_ed44810
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/id_ed448.pub1
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/id_rsa38
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/id_rsa.pub1
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key2565
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key256.pub1
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key3846
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key384.pub1
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key5217
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key521.pub1
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed25519_key7
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed25519_key.pub1
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed448_key10
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed448_key.pub1
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE.erl6
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/id_dsa21
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/id_dsa.pub1
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa2565
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa256.pub1
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa3846
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa384.pub1
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa5217
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa521.pub1
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/id_ed255197
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/id_ed25519.pub1
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/id_ed44810
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/id_ed448.pub1
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/id_rsa38
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/id_rsa.pub1
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key2565
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key256.pub1
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key3846
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key384.pub1
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key5217
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key521.pub1
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed25519_key7
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed25519_key.pub1
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed448_key10
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed448_key.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE.erl129
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed44815
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed448.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed448_key15
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed448_key.pub1
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE.erl393
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/id_dsa12
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/id_dsa.pub1
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa2565
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa256.pub1
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa3846
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa384.pub1
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa5217
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa521.pub1
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed255197
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed25519.pub1
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed44810
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed448.pub1
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/id_rsa27
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/id_rsa.pub1
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_dsa_key13
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_dsa_key.pub11
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key2565
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key256.pub1
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key3846
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key384.pub1
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key5217
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key521.pub1
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed25519_key7
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed25519_key.pub1
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed448_key10
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed448_key.pub1
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_rsa_key16
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_rsa_key.pub5
-rw-r--r--lib/ssh/test/ssh_sftpd_SUITE.erl8
-rw-r--r--lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl25
-rw-r--r--lib/ssh/test/ssh_test_lib.erl468
-rw-r--r--lib/ssh/test/ssh_test_lib.hrl10
-rw-r--r--lib/ssh/test/ssh_to_openssh_SUITE.erl66
-rw-r--r--lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ecdsa_key5
-rw-r--r--lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ecdsa_key.pub1
-rw-r--r--lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ed25519_key7
-rw-r--r--lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ed25519_key.pub1
-rw-r--r--lib/ssh/test/ssh_upgrade_SUITE.erl4
-rw-r--r--lib/ssh/vsn.mk2
-rw-r--r--lib/ssl/doc/src/notes.xml187
-rw-r--r--lib/ssl/doc/src/ssl.xml136
-rw-r--r--lib/ssl/doc/src/standards_compliance.xml52
-rw-r--r--lib/ssl/src/Makefile2
-rw-r--r--lib/ssl/src/dtls_connection.erl31
-rw-r--r--lib/ssl/src/dtls_handshake.erl16
-rw-r--r--lib/ssl/src/dtls_record.erl25
-rw-r--r--lib/ssl/src/ssl.app.src2
-rw-r--r--lib/ssl/src/ssl.erl360
-rw-r--r--lib/ssl/src/ssl_certificate.erl6
-rw-r--r--lib/ssl/src/ssl_cipher.erl94
-rw-r--r--lib/ssl/src/ssl_connection.erl19
-rw-r--r--lib/ssl/src/ssl_connection.hrl1
-rw-r--r--lib/ssl/src/ssl_handshake.erl216
-rw-r--r--lib/ssl/src/ssl_handshake.hrl12
-rw-r--r--lib/ssl/src/ssl_internal.hrl7
-rw-r--r--lib/ssl/src/ssl_record.erl34
-rw-r--r--lib/ssl/src/ssl_record.hrl2
-rw-r--r--lib/ssl/src/tls_connection.erl25
-rw-r--r--lib/ssl/src/tls_connection_1_3.erl2
-rw-r--r--lib/ssl/src/tls_handshake.erl41
-rw-r--r--lib/ssl/src/tls_handshake_1_3.erl227
-rw-r--r--lib/ssl/src/tls_record.erl121
-rw-r--r--lib/ssl/src/tls_record_1_3.erl24
-rw-r--r--lib/ssl/src/tls_server_session_ticket.erl16
-rw-r--r--lib/ssl/src/tls_v1.erl121
-rw-r--r--lib/ssl/test/Makefile1
-rw-r--r--lib/ssl/test/openssl_alpn_SUITE.erl8
-rw-r--r--lib/ssl/test/openssl_cipher_suite_SUITE.erl2
-rw-r--r--lib/ssl/test/openssl_client_cert_SUITE.erl26
-rw-r--r--lib/ssl/test/openssl_npn_SUITE.erl28
-rw-r--r--lib/ssl/test/openssl_reject_SUITE.erl24
-rw-r--r--lib/ssl/test/openssl_renegotiate_SUITE.erl34
-rw-r--r--lib/ssl/test/openssl_server_cert_SUITE.erl26
-rw-r--r--lib/ssl/test/openssl_session_SUITE.erl34
-rw-r--r--lib/ssl/test/openssl_sni_SUITE.erl8
-rw-r--r--lib/ssl/test/ssl_alpn_SUITE.erl4
-rw-r--r--lib/ssl/test/ssl_api_SUITE.erl207
-rw-r--r--lib/ssl/test/ssl_app_env_SUITE.erl10
-rw-r--r--lib/ssl/test/ssl_basic_SUITE.erl16
-rw-r--r--lib/ssl/test/ssl_cert_SUITE.erl113
-rw-r--r--lib/ssl/test/ssl_cert_tests.erl33
-rw-r--r--lib/ssl/test/ssl_cipher_suite_SUITE.erl22
-rw-r--r--lib/ssl/test/ssl_crl_SUITE.erl4
-rw-r--r--lib/ssl/test/ssl_mfl_SUITE.erl513
-rw-r--r--lib/ssl/test/ssl_npn_SUITE.erl4
-rw-r--r--lib/ssl/test/ssl_packet_SUITE.erl4
-rw-r--r--lib/ssl/test/ssl_payload_SUITE.erl13
-rw-r--r--lib/ssl/test/ssl_session_SUITE.erl2
-rw-r--r--lib/ssl/test/ssl_session_cache_SUITE.erl16
-rw-r--r--lib/ssl/test/ssl_sni_SUITE.erl29
-rw-r--r--lib/ssl/test/ssl_socket_SUITE.erl6
-rw-r--r--lib/ssl/test/ssl_test_lib.erl285
-rw-r--r--lib/ssl/test/tls_1_3_record_SUITE.erl5
-rw-r--r--lib/ssl/test/tls_api_SUITE.erl3
-rw-r--r--lib/ssl/test/x509_test.erl2
-rw-r--r--lib/ssl/vsn.mk2
-rw-r--r--lib/stdlib/doc/src/c.xml12
-rw-r--r--lib/stdlib/doc/src/gen_event.xml6
-rw-r--r--lib/stdlib/doc/src/gen_server.xml4
-rw-r--r--lib/stdlib/doc/src/gen_statem.xml6
-rw-r--r--lib/stdlib/doc/src/notes.xml224
-rw-r--r--lib/stdlib/doc/src/proc_lib.xml6
-rw-r--r--lib/stdlib/doc/src/shell.xml7
-rw-r--r--lib/stdlib/doc/src/shell_docs.xml39
-rw-r--r--lib/stdlib/examples/erl_id_trans.erl6
-rw-r--r--lib/stdlib/src/c.erl136
-rw-r--r--lib/stdlib/src/digraph.erl4
-rw-r--r--lib/stdlib/src/erl_expand_records.erl10
-rw-r--r--lib/stdlib/src/erl_internal.erl3
-rw-r--r--lib/stdlib/src/erl_lint.erl7
-rw-r--r--lib/stdlib/src/erl_pp.erl2
-rw-r--r--lib/stdlib/src/otp_internal.erl14
-rw-r--r--lib/stdlib/src/otp_internal.hrl2
-rw-r--r--lib/stdlib/src/shell_default.erl12
-rw-r--r--lib/stdlib/src/shell_docs.erl426
-rw-r--r--lib/stdlib/src/stdlib.app.src2
-rw-r--r--lib/stdlib/src/stdlib.appup.src3
-rw-r--r--lib/stdlib/test/digraph_SUITE.erl51
-rw-r--r--lib/stdlib/test/gen_statem_SUITE.erl233
-rw-r--r--lib/stdlib/test/gen_statem_SUITE_data/oc_statem.erl18
-rw-r--r--lib/stdlib/test/property_test/shell_docs_prop.erl135
-rw-r--r--lib/stdlib/test/re_SUITE_data/testoutput23
-rw-r--r--lib/stdlib/test/shell_docs_SUITE.erl131
-rw-r--r--lib/stdlib/vsn.mk2
-rw-r--r--lib/syntax_tools/doc/src/notes.xml22
-rw-r--r--lib/syntax_tools/vsn.mk2
-rw-r--r--lib/tools/doc/src/notes.xml27
-rw-r--r--lib/tools/src/tools.app.src2
-rw-r--r--lib/tools/test/fprof_SUITE.erl16
-rw-r--r--lib/tools/test/prof_bench_SUITE.erl2
-rw-r--r--lib/tools/vsn.mk2
-rw-r--r--lib/wx/c_src/egl_impl.cpp6
-rw-r--r--lib/wx/doc/src/notes.xml27
-rw-r--r--lib/wx/vsn.mk2
-rw-r--r--lib/xmerl/doc/src/notes.xml16
-rw-r--r--lib/xmerl/vsn.mk2
472 files changed, 68662 insertions, 6027 deletions
diff --git a/lib/asn1/doc/src/notes.xml b/lib/asn1/doc/src/notes.xml
index ddc115c98c..0412d40423 100644
--- a/lib/asn1/doc/src/notes.xml
+++ b/lib/asn1/doc/src/notes.xml
@@ -32,6 +32,57 @@
<p>This document describes the changes made to the asn1 application.</p>
+<section><title>Asn1 5.0.13</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Adhere to the ASN.1 specification for hstring &amp;
+ bstring lexical items. That is they may include white
+ space.</p>
+ <p>
+ Own Id: OTP-16490</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>Refactored the internal handling of deprecated and
+ removed functions.</p>
+ <p>
+ Own Id: OTP-16469</p>
+ </item>
+ <item>
+ <p>
+ Improve handling of ellipsis in a CHOICE</p>
+ <p>
+ Own Id: OTP-16554 Aux Id: ERL-1189 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Asn1 5.0.12</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Dialyzer warnings of type <c>no_match</c> are now
+ suppressed in the generated files.</p>
+ <p>
+ Own Id: OTP-16636 Aux Id: ERIERL-145 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Asn1 5.0.11</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/asn1/src/asn1ct_gen.erl b/lib/asn1/src/asn1ct_gen.erl
index 4f8b428b54..30396821b7 100644
--- a/lib/asn1/src/asn1ct_gen.erl
+++ b/lib/asn1/src/asn1ct_gen.erl
@@ -1314,7 +1314,9 @@ gen_head(#gen{options=Options}=Gen, Mod, Hrl) ->
Mod,".",nl,nl,
"-module('",Mod,"').",nl,
"-compile(nowarn_unused_vars).",nl,
- "-dialyzer(no_improper_lists).",nl]),
+ "-dialyzer(no_improper_lists).",nl,
+ "-dialyzer(no_match).",nl
+ ]),
case Hrl of
0 -> ok;
_ -> emit(["-include(\"",Mod,".hrl\").",nl])
diff --git a/lib/asn1/test/asn1_SUITE.erl b/lib/asn1/test/asn1_SUITE.erl
index ca7949fb37..f75a385cf5 100644
--- a/lib/asn1/test/asn1_SUITE.erl
+++ b/lib/asn1/test/asn1_SUITE.erl
@@ -45,10 +45,112 @@ all() ->
[xref,
xref_export_all,
- {group, compile},
- {group, parallel},
+ c_string,
+ constraint_equivalence,
+
+ ber_decode_invalid_length,
+ ber_choiceinseq,
+ ber_optional,
+ tagdefault_automatic,
+
+ cover,
+
+ parse,
+ test_undecoded_rest,
+ specialized_decodes,
+ special_decode_performance,
+
+ testMegaco,
+ testConstraints,
+ testCompactBitString,
+ default,
+ testPrim,
+ rtUI,
+ testPrimStrings,
+
+ per,
+ ber,
+ der,
+
+ h323test,
+ testExtensibilityImplied,
+ testChoice,
+ testDefaultOctetString,
+ testMultipleLevels,
+ testOpt,
+ testSeqDefault,
+ testMaps,
+
+ testTypeValueNotation,
+
+ testExternal,
+
+ testSeqExtension,
+ testSeqOptional,
+ testSeqPrim,
+ testSeqTypeRefCho,
+ testSeqTypeRefPrim,
+ testSeqTypeRefSeq,
+ testSeqTypeRefSet,
+
+ testSeqOf,
+ testSeqOfIndefinite,
+ testSeqOfCho,
+ testSeqOfChoExt,
+
+ testExtensionAdditionGroup,
+
+ testSet,
+ testSetOf,
+
+ testEnumExt,
+ value_test,
+ testSeq2738,
+ constructed,
+ ber_decode_error,
+ otp_14440,
+ testSeqSetIndefinite,
+ testChoiceIndefinite,
+ per_open_type,
+ testInfObjectClass,
+ testUniqueObjectSets,
+ testInfObjExtract,
+ testParam,
+ testFragmented,
+ testMergeCompile,
+ testobj,
+ testDeepTConstr,
+ testImport,
+ testDER,
+ testDEFAULT,
+ testExtensionDefault,
+ testMvrasn6,
+ testContextSwitchingTypes,
+ testOpenTypeImplicitTag,
+ testROSE,
+ testINSTANCE_OF,
+ testTCAP,
+ test_ParamTypeInfObj,
+ test_Defed_ObjectIdentifier,
+ testSelectionType,
+ testSSLspecs,
+ testNortel,
+ test_WS_ParamClass,
+ test_modified_x420,
+
+ %% Some heavy tests.
+ testTcapsystem,
+ testNBAPsystem,
+ testS1AP,
+ testRfcs,
+
+ test_compile_options,
+ testDoubleEllipses,
+ test_x691,
+ ticket_6143,
+ test_OTP_9688,
+ testValueTest,
- % TODO: Investigate parallel running of these:
testComment,
testName2Number,
ticket_7407,
@@ -57,129 +159,7 @@ all() ->
{group, performance}].
groups() ->
- Parallel = asn1_test_lib:parallel(),
- [{compile, Parallel,
- [c_string,
- constraint_equivalence]},
-
- {ber, Parallel,
- [ber_decode_invalid_length,
- ber_choiceinseq,
- % Uses 'SOpttest'
- ber_optional,
- tagdefault_automatic]},
-
- {parallel, Parallel,
- [cover,
- {group, ber},
- % Uses 'P-Record', 'Constraints', 'MEDIA-GATEWAY-CONTROL'...
- {group, [], [parse,
- test_undecoded_rest,
- specialized_decodes,
- special_decode_performance,
- testMegaco,
- testConstraints,
- testCompactBitString]},
- default,
- % Uses 'Def', 'MULTIMEDIA-SYSTEM-CONTROL', 'H323-MESSAGES', 'Prim',
- % 'Real'
- {group, [], [testPrim,
- rtUI,
- testPrimStrings,
- per,
- ber_other,
- der,
- h323test]},
- testExtensibilityImplied,
- testChoPrim,
- testChoExtension,
- testChoOptional,
- testChoRecursive,
- testChoTypeRefCho,
- testChoTypeRefPrim,
- testChoTypeRefSeq,
- testChoTypeRefSet,
- testDefaultOctetString,
- testMultipleLevels,
- testOpt,
- testSeqDefault,
- testMaps,
- % Uses 'External'
- {group, [], [testExternal,
- testSeqExtension]},
- testSeqOptional,
- testSeqPrim,
- testSeqTypeRefCho,
- % Uses 'SeqTypeRefPrim'
- {group, [], [testSeqTypeRefPrim,
- testTypeValueNotation]},
- testSeqTypeRefSeq,
- testSeqTypeRefSet,
- % Uses 'SeqOf'
- {group, [], [testSeqOf,
- testSeqOfIndefinite]}, % Uses 'Mvrasn*'
- testSeqOfCho,
- testSeqOfChoExt,
- testSetDefault,
- testExtensionAdditionGroup,
- testSetOptional,
- testSetPrim,
- testSetTypeRefCho,
- testSetTypeRefPrim,
- testSetTypeRefSeq,
- testSetTypeRefSet,
- testSetOf,
- testSetOfCho,
- testEnumExt,
- value_test,
- testSeq2738,
- % Uses 'Constructed'
- {group, [], [constructed,
- ber_decode_error,
- otp_14440]},
- testSeqSetIndefinite,
- testChoiceIndefinite,
- per_open_type,
- testInfObjectClass,
- testUniqueObjectSets,
- testInfObjExtract,
- testParam,
- testFragmented,
- testMergeCompile,
- testobj,
- testDeepTConstr,
- testImport,
- testDER,
- testDEFAULT,
- testExtensionDefault,
- testMvrasn6,
- testContextSwitchingTypes,
- testOpenTypeImplicitTag,
- testROSE,
- testINSTANCE_OF,
- testTCAP,
- test_ParamTypeInfObj,
- test_Defed_ObjectIdentifier,
- testSelectionType,
- testSSLspecs,
- testNortel,
- % Uses 'PKCS7', 'InformationFramework'
- {group, [], [test_WS_ParamClass,
- test_modified_x420]},
- %% Don't run all these at the same time.
- {group, [],
- [testTcapsystem,
- testNBAPsystem,
- testS1AP,
- testRfcs]},
- test_compile_options,
- testDoubleEllipses,
- test_x691,
- ticket_6143,
- test_OTP_9688,
- testValueTest]},
-
- {performance, [],
+ [{performance, [],
[testTimer_ber,
testTimer_ber_maps,
testTimer_per,
@@ -326,30 +306,26 @@ do_test_prim(Rule, NoOkWrapper) ->
testCompactBitString(Config) -> test(Config, fun testCompactBitString/3).
testCompactBitString(Config, Rule, Opts) ->
- asn1_test_lib:compile("PrimStrings", Config,
- [Rule, compact_bit_string|Opts]),
+ Files = ["PrimStrings", "Constraints"],
+ asn1_test_lib:compile_all(Files, Config, [Rule, compact_bit_string|Opts]),
testCompactBitString:compact_bit_string(Rule),
testCompactBitString:bit_string_unnamed(Rule),
testCompactBitString:bit_string_unnamed(Rule),
testCompactBitString:ticket_7734(Rule),
- asn1_test_lib:compile("Constraints", Config,
- [Rule, compact_bit_string|Opts]),
testCompactBitString:otp_4869(Rule).
testPrimStrings(Config) ->
test(Config, fun testPrimStrings/3, [ber,{ber,[der]},per,uper]).
testPrimStrings(Config, Rule, Opts) ->
LegacyOpts = [legacy_erlang_types|Opts],
- asn1_test_lib:compile_all(["PrimStrings", "BitStr"], Config,
- [Rule|LegacyOpts]),
+ Files = ["PrimStrings", "BitStr"],
+ asn1_test_lib:compile_all(Files, Config, [Rule|LegacyOpts]),
testPrimStrings_cases(Rule, LegacyOpts),
- asn1_test_lib:compile_all(["PrimStrings", "BitStr"], Config, [Rule|Opts]),
+ asn1_test_lib:compile_all(Files, Config, [Rule|Opts]),
testPrimStrings_cases(Rule, Opts),
- asn1_test_lib:compile_all(["PrimStrings", "BitStr"], Config,
- [legacy_bit_string,Rule|Opts]),
+ asn1_test_lib:compile_all(Files, Config, [legacy_bit_string,Rule|Opts]),
testPrimStrings:bit_string(Rule, Opts),
- asn1_test_lib:compile_all(["PrimStrings", "BitStr"], Config,
- [compact_bit_string,Rule|Opts]),
+ asn1_test_lib:compile_all(Files, Config, [compact_bit_string,Rule|Opts]),
testPrimStrings:bit_string(Rule, Opts),
testPrimStrings:more_strings(Rule).
@@ -398,46 +374,26 @@ testExtensibilityImplied(Config, Rule, Opts) ->
[Rule,no_ok_wrapper|Opts]),
testExtensibilityImplied:main(Rule).
-testChoPrim(Config) -> test(Config, fun testChoPrim/3).
-testChoPrim(Config, Rule, Opts) ->
- asn1_test_lib:compile("ChoPrim", Config, [Rule|Opts]),
+testChoice(Config) -> test(Config, fun testChoice/3).
+testChoice(Config, Rule, Opts) ->
+ Files = ["ChoPrim",
+ "ChoExtension",
+ "ChoOptional",
+ "ChoOptionalImplicitTag",
+ "ChoRecursive",
+ "ChoTypeRefCho",
+ "ChoTypeRefPrim",
+ "ChoTypeRefSeq",
+ "ChoTypeRefSet"],
+ asn1_test_lib:compile_all(Files, Config, [Rule|Opts]),
testChoPrim:bool(Rule),
- testChoPrim:int(Rule).
-
-testChoExtension(Config) -> test(Config, fun testChoExtension/3).
-testChoExtension(Config, Rule, Opts) ->
- asn1_test_lib:compile("ChoExtension", Config, [Rule|Opts]),
- testChoExtension:extension(Rule).
-
-testChoOptional(Config) -> test(Config, fun testChoOptional/3).
-testChoOptional(Config, Rule, Opts) ->
- asn1_test_lib:compile_all(["ChoOptional",
- "ChoOptionalImplicitTag"], Config, [Rule|Opts]),
- testChoOptional:run().
-
-testChoRecursive(Config) -> test(Config, fun testChoRecursive/3).
-testChoRecursive(Config, Rule, Opts) ->
- asn1_test_lib:compile("ChoRecursive", Config, [Rule|Opts]),
- testChoRecursive:recursive(Rule).
-
-testChoTypeRefCho(Config) -> test(Config, fun testChoTypeRefCho/3).
-testChoTypeRefCho(Config, Rule, Opts) ->
- asn1_test_lib:compile("ChoTypeRefCho", Config, [Rule|Opts]),
- testChoTypeRefCho:choice(Rule).
-
-testChoTypeRefPrim(Config) -> test(Config, fun testChoTypeRefPrim/3).
-testChoTypeRefPrim(Config, Rule, Opts) ->
- asn1_test_lib:compile("ChoTypeRefPrim", Config, [Rule|Opts]),
- testChoTypeRefPrim:prim(Rule).
-
-testChoTypeRefSeq(Config) -> test(Config, fun testChoTypeRefSeq/3).
-testChoTypeRefSeq(Config, Rule, Opts) ->
- asn1_test_lib:compile("ChoTypeRefSeq", Config, [Rule|Opts]),
- testChoTypeRefSeq:seq(Rule).
-
-testChoTypeRefSet(Config) -> test(Config, fun testChoTypeRefSet/3).
-testChoTypeRefSet(Config, Rule, Opts) ->
- asn1_test_lib:compile("ChoTypeRefSet", Config, [Rule|Opts]),
+ testChoPrim:int(Rule),
+ testChoExtension:extension(Rule),
+ testChoOptional:run(),
+ testChoRecursive:recursive(Rule),
+ testChoTypeRefCho:choice(Rule),
+ testChoTypeRefPrim:prim(Rule),
+ testChoTypeRefSeq:seq(Rule),
testChoTypeRefSet:set(Rule).
testDefaultOctetString(Config) -> test(Config, fun testDefaultOctetString/3).
@@ -564,50 +520,33 @@ testSeqOfIndefinite(Config, Rule, Opts) ->
asn1_test_lib:compile_all(Files, Config, [Rule|Opts]),
testSeqOfIndefinite:main().
-testSetDefault(Config) -> test(Config, fun testSetDefault/3).
-testSetDefault(Config, Rule, Opts) ->
- asn1_test_lib:compile("SetDefault", Config, [Rule|Opts]),
- testSetDefault:main(Rule).
+testSet(Config) -> test(Config, fun testSet/3).
+testSet(Config, Rule, Opts) ->
+ Files = ["SetDefault",
+ "SetOptional",
+ "SetPrim",
+ "SetTypeRefCho",
+ "SetTypeRefPrim",
+ "SetTypeRefSeq",
+ "SetTypeRefSet"],
+ asn1_test_lib:compile_all(Files, Config, [Rule|Opts]),
-testSetOptional(Config) -> test(Config, fun testSetOptional/3).
-testSetOptional(Config, Rule, Opts) ->
- asn1_test_lib:compile("SetOptional", Config, [Rule|Opts]),
+ testSetDefault:main(Rule),
testSetOptional:ticket_7533(Rule),
- testSetOptional:main(Rule).
-
-testSetPrim(Config) -> test(Config, fun testSetPrim/3).
-testSetPrim(Config, Rule, Opts) ->
- asn1_test_lib:compile("SetPrim", Config, [Rule|Opts]),
- testSetPrim:main(Rule).
-
-testSetTypeRefCho(Config) -> test(Config, fun testSetTypeRefCho/3).
-testSetTypeRefCho(Config, Rule, Opts) ->
- asn1_test_lib:compile("SetTypeRefCho", Config, [Rule|Opts]),
- testSetTypeRefCho:main(Rule).
-
-testSetTypeRefPrim(Config) -> test(Config, fun testSetTypeRefPrim/3).
-testSetTypeRefPrim(Config, Rule, Opts) ->
- asn1_test_lib:compile("SetTypeRefPrim", Config, [Rule|Opts]),
- testSetTypeRefPrim:main(Rule).
-
-testSetTypeRefSeq(Config) -> test(Config, fun testSetTypeRefSeq/3).
-testSetTypeRefSeq(Config, Rule, Opts) ->
- asn1_test_lib:compile("SetTypeRefSeq", Config, [Rule|Opts]),
- testSetTypeRefSeq:main(Rule).
-
-testSetTypeRefSet(Config) -> test(Config, fun testSetTypeRefSet/3).
-testSetTypeRefSet(Config, Rule, Opts) ->
- asn1_test_lib:compile("SetTypeRefSet", Config, [Rule|Opts]),
+ testSetOptional:main(Rule),
+
+ testSetPrim:main(Rule),
+ testSetTypeRefCho:main(Rule),
+ testSetTypeRefPrim:main(Rule),
+ testSetTypeRefSeq:main(Rule),
testSetTypeRefSet:main(Rule).
testSetOf(Config) -> test(Config, fun testSetOf/3).
testSetOf(Config, Rule, Opts) ->
- asn1_test_lib:compile("SetOf", Config, [Rule|Opts]),
- testSetOf:main(Rule).
-
-testSetOfCho(Config) -> test(Config, fun testSetOfCho/3).
-testSetOfCho(Config, Rule, Opts) ->
- asn1_test_lib:compile("SetOfCho", Config, [Rule|Opts]),
+ Files = ["SetOf",
+ "SetOfCho"],
+ asn1_test_lib:compile_all(Files, Config, [Rule|Opts]),
+ testSetOf:main(Rule),
testSetOfCho:main(Rule).
c_string(Config) ->
@@ -657,19 +596,23 @@ parse(Config) ->
per(Config) ->
test(Config, fun per/3, [per,uper,{per,[maps]},{uper,[maps]}]).
per(Config, Rule, Opts) ->
- [module_test(M, Config, Rule, Opts) || M <- per_modules()].
+ module_test(per_modules(), Config, Rule, Opts).
-ber_other(Config) ->
- test(Config, fun ber_other/3, [ber,{ber,[maps]}]).
+ber(Config) ->
+ test(Config, fun ber/3, [ber,{ber,[maps]}]).
-ber_other(Config, Rule, Opts) ->
- [module_test(M, Config, Rule, Opts) || M <- ber_modules()].
+ber(Config, Rule, Opts) ->
+ module_test(ber_modules(), Config, Rule, Opts).
der(Config) ->
asn1_test_lib:compile_all(ber_modules(), Config, [der]).
-module_test(M0, Config, Rule, Opts) ->
- asn1_test_lib:compile(M0, Config, [Rule,?NO_MAPS_MODULE|Opts]),
+module_test(Modules, Config, Rule, Opts) ->
+ asn1_test_lib:compile_all(Modules, Config, [Rule,?NO_MAPS_MODULE|Opts]),
+ _ = [do_module_test(M, Config, Opts) || M <- Modules],
+ ok.
+
+do_module_test(M0, Config, Opts) ->
case list_to_atom(M0) of
'LDAP' ->
%% Because of the recursive definition of 'Filter' in
@@ -815,8 +758,8 @@ per_open_type(Config, Rule, Opts) ->
testConstraints(Config) -> test(Config, fun testConstraints/3).
testConstraints(Config, Rule, Opts) ->
- asn1_test_lib:compile("Constraints", Config, [Rule|Opts]),
- asn1_test_lib:compile("LargeConstraints", Config, [Rule|Opts]),
+ Files = ["Constraints", "LargeConstraints"],
+ asn1_test_lib:compile_all(Files, Config, [Rule|Opts]),
testConstraints:int_constraints(Rule),
case Rule of
ber -> ok;
@@ -1163,9 +1106,6 @@ testExtensionAdditionGroup(Config, Rule, Opts) ->
[Rule,{record_name_prefix,"RRC-"}|Opts]),
extensionAdditionGroup:run(Rule).
-% parse_modules() ->
-% ["ImportsFrom"].
-
per_modules() ->
[X || X <- test_modules()].
diff --git a/lib/asn1/test/asn1_test_lib.erl b/lib/asn1/test/asn1_test_lib.erl
index af8462f0c9..2c91256d6d 100644
--- a/lib/asn1/test/asn1_test_lib.erl
+++ b/lib/asn1/test/asn1_test_lib.erl
@@ -24,7 +24,6 @@
rm_dirs/1,
hex_to_bin/1,
match_value/2,
- parallel/0,
roundtrip/3,roundtrip/4,roundtrip_enc/3,roundtrip_enc/4,
map_roundtrip/3]).
@@ -48,12 +47,6 @@ compile_all(Files, Config, Options0) ->
dialyze(Files, Options),
ok.
-parallel() ->
- case erlang:system_info(schedulers) > 1 andalso not run_dialyzer() of
- true -> [parallel];
- false -> []
- end.
-
dialyze(Files, Options) ->
case not run_dialyzer() orelse lists:member(abs, Options) of
true -> ok;
diff --git a/lib/asn1/test/testEnumExt.erl b/lib/asn1/test/testEnumExt.erl
index b30750f7fc..cb01ad481f 100644
--- a/lib/asn1/test/testEnumExt.erl
+++ b/lib/asn1/test/testEnumExt.erl
@@ -41,7 +41,7 @@ main(Rule) when Rule =:= per; Rule =:= uper ->
B64 = roundtrip('Noext', red),
common(Rule);
main(Rule) when Rule =:= ber; Rule =:= jer ->
- io:format("main(ber)~n",[]),
+ io:format("main(~p)~n",[Rule]),
%% ENUMERATED with extensionmark (value is in root set)
roundtrip('Ext', red),
diff --git a/lib/asn1/test/testMegaco.erl b/lib/asn1/test/testMegaco.erl
index 0be798b962..6a88117896 100644
--- a/lib/asn1/test/testMegaco.erl
+++ b/lib/asn1/test/testMegaco.erl
@@ -26,8 +26,8 @@
-include_lib("common_test/include/ct.hrl").
compile(Config, Erule, Options) ->
- asn1_test_lib:compile("MEDIA-GATEWAY-CONTROL.asn", Config, [Erule|Options]),
- asn1_test_lib:compile("OLD-MEDIA-GATEWAY-CONTROL.asn", Config, [Erule|Options]),
+ Files = ["MEDIA-GATEWAY-CONTROL.asn","OLD-MEDIA-GATEWAY-CONTROL.asn"],
+ asn1_test_lib:compile_all(Files, Config, [Erule|Options]),
{ok,'OLD-MEDIA-GATEWAY-CONTROL','MEDIA-GATEWAY-CONTROL'}.
main(no_module,_) -> ok;
diff --git a/lib/asn1/vsn.mk b/lib/asn1/vsn.mk
index e1d3b65da2..8ebbe907bc 100644
--- a/lib/asn1/vsn.mk
+++ b/lib/asn1/vsn.mk
@@ -1 +1 @@
-ASN1_VSN = 5.0.11
+ASN1_VSN = 5.0.13
diff --git a/lib/common_test/doc/src/notes.xml b/lib/common_test/doc/src/notes.xml
index 70653c0711..9545d8352e 100644
--- a/lib/common_test/doc/src/notes.xml
+++ b/lib/common_test/doc/src/notes.xml
@@ -33,6 +33,44 @@
<file>notes.xml</file>
</header>
+<section><title>Common_Test 1.19</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ The function <c>ct_property_test:init_tool/1</c> is added
+ for the cases when the user does not want
+ ct_property_test to compile properties. init_tool/1 can
+ be used to set the property_test_tool config value.</p>
+ <p>
+ Own Id: OTP-16029 Aux Id: PR-2145 </p>
+ </item>
+ <item>
+ <p>
+ The built-in Common Test Hook, <c>cth_log_redirect</c>,
+ has been updated to use the system <c>default</c> Logger
+ handler's configuration instead of its own. See the
+ section on <seeguide
+ marker="common_test:ct_hooks_chapter#built-in-cths">Built-in
+ Hooks</seeguide> in the Common Test User's Guide.</p>
+ <p>
+ Own Id: OTP-16273</p>
+ </item>
+ <item>
+ <p>
+ Calls of deprecated functions in the <seeguide
+ marker="crypto:new_api#the-old-api">Old Crypto
+ API</seeguide> are replaced by calls of their <seeguide
+ marker="crypto:new_api#the-new-api">substitutions</seeguide>.</p>
+ <p>
+ Own Id: OTP-16346</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Common_Test 1.18.2</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/common_test/vsn.mk b/lib/common_test/vsn.mk
index 1e6991d73a..b5fa287e1f 100644
--- a/lib/common_test/vsn.mk
+++ b/lib/common_test/vsn.mk
@@ -1 +1 @@
-COMMON_TEST_VSN = 1.18.2
+COMMON_TEST_VSN = 1.19
diff --git a/lib/compiler/doc/src/notes.xml b/lib/compiler/doc/src/notes.xml
index 079c84b776..a37ac251de 100644
--- a/lib/compiler/doc/src/notes.xml
+++ b/lib/compiler/doc/src/notes.xml
@@ -32,6 +32,129 @@
<p>This document describes the changes made to the Compiler
application.</p>
+<section><title>Compiler 7.6.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ In rare circumstances, a guard using 'not' could evaluate
+ to the wrong boolean value.</p>
+ <p>
+ Own Id: OTP-16652 Aux Id: ERL-1246 </p>
+ </item>
+ <item>
+ <p>A guard expression that referenced a variable bound to
+ a boolean expression could evaluate to the wrong
+ value.</p>
+ <p>
+ Own Id: OTP-16657 Aux Id: ERL-1253 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Compiler 7.6</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p><c>erlang:fun_info(fun foo/1, name/1)</c> used to
+ return a function name based on the name of the function
+ that <c>fun foo/1</c> was used in. The name returned is
+ now <c>-fun.foo/1-</c>.</p>
+ <p>
+ Own Id: OTP-15837</p>
+ </item>
+ <item>
+ <p> Initialization of record fields using <c>_</c> is no
+ longer allowed if the number of affected fields is zero.
+ </p>
+ <p>
+ Own Id: OTP-16516</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>EEP-52 has been implemented.</p>
+ <p>In binary matching, the size of the segment to be
+ matched is now allowed to be a guard expression, and
+ similarly in map matching the keys can now be guard
+ expressions. See the Erlang Reference Manual and
+ Programming Examples for more details.</p>
+ <p>Language compilers or code generators that generate
+ Core Erlang code may need to be updated to be compatible
+ with the compiler in OTP 23. For more details, see the
+ section Backwards Compatibility in <url
+ href="http://erlang.org/eeps/eep-0052.html">EEP
+ 52</url>.</p>
+ <p>
+ Own Id: OTP-14708</p>
+ </item>
+ <item>
+ <p> Allow underscores in numeric literals to improve
+ readability. Examples: <c>123_456_789</c>,
+ <c>16#1234_ABCD</c>. </p>
+ <p>
+ Own Id: OTP-16007 Aux Id: PR-2324 </p>
+ </item>
+ <item>
+ <p>Improved the type optimization pass' inference of
+ types that depend on themselves, giving us more accurate
+ types and letting us track the content types of
+ lists.</p>
+ <p>
+ Own Id: OTP-16214 Aux Id: PR-2460 </p>
+ </item>
+ <item>
+ <p>
+ Support message queue optimization also for references
+ returned from the new <seemfa
+ marker="erts:erlang#spawn_request/5"><c>spawn_request()</c></seemfa>
+ BIFs.</p>
+ <p>
+ Own Id: OTP-16367 Aux Id: OTP-15251 </p>
+ </item>
+ <item>
+ <p>The compiler will now raise a warning when inlining is
+ used in modules that load NIFs.</p>
+ <p>
+ Own Id: OTP-16429 Aux Id: ERL-303 </p>
+ </item>
+ <item>
+ <p>Refactored the internal handling of deprecated and
+ removed functions.</p>
+ <p>
+ Own Id: OTP-16469</p>
+ </item>
+ <item>
+ <p>Line information was sometimes incorrect for
+ floating-point math exceptions.</p>
+ <p>
+ Own Id: OTP-16505 Aux Id: ERL-1178 </p>
+ </item>
+ <item>
+ <p>The <c>debug_info</c> option can now be specified in
+ <c>-compile()</c> attributes.</p>
+ <p>
+ Own Id: OTP-16523 Aux Id: ERL-1058 </p>
+ </item>
+ <item>
+ <p>Reduced the resource usage of <c>erlc</c> in parallel
+ builds (e.g. <c>make -j128</c>).</p>
+ <p>
+ Own Id: OTP-16543 Aux Id: ERL-1186 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Compiler 7.5.4</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/compiler/src/beam_ssa_bool.erl b/lib/compiler/src/beam_ssa_bool.erl
index 7ae9070df2..5b81ca2be1 100644
--- a/lib/compiler/src/beam_ssa_bool.erl
+++ b/lib/compiler/src/beam_ssa_bool.erl
@@ -922,9 +922,11 @@ opt_digraph_instr(#b_set{dst=Dst}=I, G0, St) ->
%% Rewriting 'xor' is not practical. Fortunately,
%% 'xor' is almost never used in practice.
not_possible();
- #b_set{op={bif,'not'},args=[#b_var{}=Bool]} ->
- G = convert_to_br_node(I, Fail, G1, St),
- redirect_test(Bool, {fail,Succ}, G, St);
+ #b_set{op={bif,'not'}} ->
+ %% This is suprisingly rare. The previous attempt to
+ %% optimize it was broken, which wasn't noticed because
+ %% very few test cases triggered this code.
+ not_possible();
#b_set{op=phi,dst=Bool} ->
Vtx = get_vertex(Bool, St),
G2 = del_out_edges(Vtx, G1),
@@ -1070,7 +1072,60 @@ redirect_phi(Phi, Args, SuccFail, G0, St) ->
redirect_phi_1(PhiVtx, [{#b_literal{val=false},FalseExit},
{#b_var{}=SuccBool,_BoolExit}],
SuccFail, G0, St) ->
+ %% This was most likely an `andalso` in the source code.
BoolVtx = get_vertex(SuccBool, St),
+
+ %% We must be careful when rewriting guards that reference boolean
+ %% expressions defined before the guard. Here is an example:
+ %%
+ %% Bool = Z =:= false,
+ %% if
+ %% X =:= Y andalso Bool -> ok;
+ %% true -> error
+ %% end.
+ %%
+ %% Slightly simplified, the SSA code will look like this:
+ %%
+ %% 10: Bool = bif:'=:=' _2, `false`
+ %% br ^11
+ %%
+ %% 11: B = bif:'=:=' X, Y
+ %% br B, ^20, ^30
+ %%
+ %% 20: br ^40
+ %% 30: br ^40
+ %%
+ %% 40: Phi = phi { `true`, ^20 }, { Bool, ^30 }
+ %% br Phi, ^100, ^200
+ %%
+ %% 100: ret `ok`
+ %% 200: ret `error'
+ %%
+ %% The usual rewriting of the phi node will result in the following
+ %% SSA code:
+ %%
+ %% 10: Bool = bif:'=:=' _2, `false`
+ %% br Bool, ^100, ^200
+ %%
+ %% 11: B = bif:'=:=' X, Y
+ %% br B, ^100, ^200
+ %%
+ %% 20: br ^40
+ %% 30: br ^40
+ %%
+ %% 40: Phi = phi { `true`, ^20 }, { Bool, ^30 }
+ %% br Phi, ^100, ^200
+ %%
+ %% 100: ret `ok`
+ %% 200: ret `error'
+ %%
+ %% Block 11 is no longer reachable; thus, the X =:= Y test has been dropped.
+ %% To avoid dropping tests, we should check whether if there is a path from
+ %% 10 to block 20. If there is, the optimization in its current form is not
+ %% safe.
+ %%
+ ensure_disjoint_paths(G0, BoolVtx, FalseExit),
+
[FalseOut] = beam_digraph:out_edges(G0, FalseExit),
G1 = beam_digraph:del_edge(G0, FalseOut),
case SuccFail of
@@ -1088,6 +1143,11 @@ redirect_phi_1(PhiVtx, [{#b_literal{val=true},TrueExit},
{fail,Fail}, G0, St) ->
%% This was probably an `orelse` in the source code.
BoolVtx = get_vertex(SuccBool, St),
+
+ %% See the previous clause for an explanation of why we
+ %% must ensure that paths are disjoint.
+ ensure_disjoint_paths(G0, BoolVtx, TrueExit),
+
[TrueOut] = beam_digraph:out_edges(G0, TrueExit),
G1 = beam_digraph:del_edge(G0, TrueOut),
G2 = beam_digraph:add_edge(G1, TrueExit, PhiVtx, next),
@@ -1117,6 +1177,18 @@ digraph_bool_def(G) ->
Ds = [{Dst,Vtx} || {Vtx,#b_set{dst=Dst}} <- Vs],
maps:from_list(Ds).
+
+%% ensure_disjoint_paths(G, Vertex1, Vertex2) -> ok.
+%% Ensure that there is no path from Vertex1 to Vertex2 in
+%% either direction. (It is probably overkill to test both
+%% directions, but better safe than sorry.)
+
+ensure_disjoint_paths(G, V1, V2) ->
+ case beam_digraph:is_path(G, V1, V2) orelse beam_digraph:is_path(G, V2, V1) of
+ true -> not_possible();
+ false -> ok
+ end.
+
%%%
%%% Shortcut branches that branch to other branches.
%%%
diff --git a/lib/compiler/src/beam_ssa_opt.erl b/lib/compiler/src/beam_ssa_opt.erl
index 1549b16cbc..d77b7756e8 100644
--- a/lib/compiler/src/beam_ssa_opt.erl
+++ b/lib/compiler/src/beam_ssa_opt.erl
@@ -40,7 +40,8 @@
-include("beam_ssa_opt.hrl").
-import(lists, [all/2,append/1,duplicate/2,flatten/1,foldl/3,
- keyfind/3,last/1,member/2,reverse/1,reverse/2,
+ keyfind/3,last/1,mapfoldl/3,member/2,
+ partition/2,reverse/1,reverse/2,
splitwith/2,sort/1,takewhile/2,unzip/1]).
-define(MAX_REPETITIONS, 16).
@@ -299,6 +300,7 @@ epilogue_passes(Opts) ->
?PASS(ssa_opt_blockify),
?PASS(ssa_opt_merge_blocks),
?PASS(ssa_opt_get_tuple_element),
+ ?PASS(ssa_opt_tail_calls),
?PASS(ssa_opt_trim_unreachable),
?PASS(ssa_opt_unfold_literals)],
passes_1(Ps, Opts).
@@ -2185,10 +2187,55 @@ opt_ne_single_use(Var, Uses) when is_map(Uses) ->
%%% extracting tuple elements on execution paths that never use the
%%% extracted values.
%%%
+%%% However, there is one caveat to be aware of. Sinking tuple elements
+%%% will keep the entire tuple alive longer. In rare circumstance, that
+%%% can be a problem. Here is an example:
+%%%
+%%% mapfoldl(F, Acc0, [Hd|Tail]) ->
+%%% {R,Acc1} = F(Hd, Acc0),
+%%% {Rs,Acc2} = mapfoldl(F, Acc1, Tail),
+%%% {[R|Rs],Acc2};
+%%% mapfoldl(F, Acc, []) ->
+%%% {[],Acc}.
+%%%
+%%% Sinking get_tuple_element instructions will generate code similar
+%%% to this example:
+%%%
+%%% slow_mapfoldl(F, Acc0, [Hd|Tail]) ->
+%%% Res1 = F(Hd, Acc0),
+%%% Res2 = slow_mapfoldl(F, element(2, Res1), Tail),
+%%% {[element(1, Res1)|element(1, Res2)],element(2, Res2)};
+%%% slow_mapfoldl(F, Acc, []) ->
+%%% {[],Acc}.
+%%%
+%%% Here the entire tuple bound to `Res1` will be kept alive until
+%%% `slow_mapfoldl/3` returns. That is, every intermediate accumulator
+%%% will be kept alive.
+%%%
+%%% In this case, not sinking is clearly superior:
+%%%
+%%% fast_mapfoldl(F, Acc0, [Hd|Tail]) ->
+%%% Res1 = F(Hd, Acc0),
+%%% R = element(1, Res1),
+%%% Res2 = fast_mapfoldl(F, element(2, Res1), Tail),
+%%% {[R|element(1, Res2)],element(2, Res2)};
+%%% fast_mapfoldl(F, Acc, []) ->
+%%% {[],Acc}.
+%%%
+%%% The `slow_mapfoldl/3` and `fast_mapfoldl/3` use the same number of
+%%% stack slots.
+%%%
+%%% To avoid producing code similar to `slow_mapfoldl/3`, use an
+%%% heuristic to only sink when sinking would reduce the number of
+%%% stack slots, or if there can't possibly be a recursive call
+%%% involved. This is a heuristic because it is difficult to exactly
+%%% predict the number of stack slots that will be needed for a given
+%%% piece of code.
ssa_opt_sink({#opt_st{ssa=Linear}=St, FuncDb}) ->
%% Create a map with all variables that define get_tuple_element
- %% instructions. The variable name map to the block it is defined in.
+ %% instructions. The variable name maps to the block it is defined
+ %% in and the source tuple.
case def_blocks(Linear) of
[] ->
%% No get_tuple_element instructions, so there is nothing to do.
@@ -2199,32 +2246,36 @@ ssa_opt_sink({#opt_st{ssa=Linear}=St, FuncDb}) ->
end.
do_ssa_opt_sink(Defs, #opt_st{ssa=Linear}=St) ->
- Blocks0 = maps:from_list(Linear),
-
- %% Now find all the blocks that use variables defined by get_tuple_element
- %% instructions.
+ %% Find all the blocks that use variables defined by
+ %% get_tuple_element instructions.
Used = used_blocks(Linear, Defs, []),
%% Calculate dominators.
+ Blocks0 = maps:from_list(Linear),
{Dom,Numbering} = beam_ssa:dominators(Blocks0),
%% It is not safe to move get_tuple_element instructions to blocks
%% that begin with certain instructions. It is also unsafe to move
- %% the instructions into any part of a receive. To avoid such
- %% unsafe moves, pretend that the unsuitable blocks are not
- %% dominators.
+ %% the instructions into any part of a receive.
Unsuitable = unsuitable(Linear, Blocks0),
%% Calculate new positions for get_tuple_element instructions. The new
%% position is a block that dominates all uses of the variable.
- DefLoc = new_def_locations(Used, Defs, Dom, Numbering, Unsuitable),
+ DefLocs0 = new_def_locations(Used, Defs, Dom, Numbering, Unsuitable),
+
+ %% Avoid keeping tuples alive if only one element is accessed later and
+ %% if there is the chance of a recursive call being made. This is an
+ %% important precaution to avoid that lists:mapfoldl/3 keeps all previous
+ %% versions of the accumulator alive until the end of the input list.
+ Ps = partition_deflocs(DefLocs0, Defs, Blocks0),
+ DefLocs1 = filter_deflocs(Ps, Blocks0),
+ DefLocs = sort(DefLocs1),
%% Now move all suitable get_tuple_element instructions to their
%% new blocks.
- Blocks = foldl(fun({V,To}, A) ->
- From = map_get(V, Defs),
+ Blocks = foldl(fun({V,{From,To}}, A) ->
move_defs(V, From, To, A)
- end, Blocks0, DefLoc),
+ end, Blocks0, DefLocs),
St#opt_st{ssa=beam_ssa:linearize(Blocks)}.
@@ -2232,8 +2283,8 @@ def_blocks([{L,#b_blk{is=Is}}|Bs]) ->
def_blocks_is(Is, L, def_blocks(Bs));
def_blocks([]) -> [].
-def_blocks_is([#b_set{op=get_tuple_element,dst=Dst}|Is], L, Acc) ->
- def_blocks_is(Is, L, [{Dst,L}|Acc]);
+def_blocks_is([#b_set{op=get_tuple_element,args=[Tuple,_],dst=Dst}|Is], L, Acc) ->
+ def_blocks_is(Is, L, [{Dst,{L,Tuple}}|Acc]);
def_blocks_is([_|Is], L, Acc) ->
def_blocks_is(Is, L, Acc);
def_blocks_is([], _, Acc) -> Acc.
@@ -2245,6 +2296,179 @@ used_blocks([{L,Blk}|Bs], Def, Acc0) ->
used_blocks([], _Def, Acc) ->
rel2fam(Acc).
+%% Partition sinks for get_tuple_element instructions in the same
+%% clause extracting from the same tuple. Sort each partition in
+%% execution order.
+partition_deflocs(DefLoc, _Defs, Blocks) ->
+ {BlkNums0,_} = mapfoldl(fun(L, N) -> {{L,N},N+1} end, 0, beam_ssa:rpo(Blocks)),
+ BlkNums = maps:from_list(BlkNums0),
+ S = [{Tuple,{map_get(To, BlkNums),{V,{From,To}}}} ||
+ {V,Tuple,{From,To}} <- DefLoc],
+ F = rel2fam(S),
+ partition_deflocs_1(F, Blocks).
+
+partition_deflocs_1([{Tuple,DefLocs0}|T], Blocks) ->
+ DefLocs1 = [DL || {_,DL} <- DefLocs0],
+ DefLocs = partition_dl(DefLocs1, Blocks),
+ [{Tuple,DL} || DL <- DefLocs] ++ partition_deflocs_1(T, Blocks);
+partition_deflocs_1([], _) -> [].
+
+partition_dl([_]=DefLoc, _Blocks) ->
+ [DefLoc];
+partition_dl([{_,{_,First}}|_]=DefLoc0, Blocks) ->
+ RPO = beam_ssa:rpo([First], Blocks),
+ {P,DefLoc} = partition_dl_1(DefLoc0, RPO, []),
+ [P|partition_dl(DefLoc, Blocks)];
+partition_dl([], _Blocks) -> [].
+
+partition_dl_1([{_,{_,L}}=DL|DLs], [L|_]=Ls, Acc) ->
+ partition_dl_1(DLs, Ls, [DL|Acc]);
+partition_dl_1([_|_]=DLs, [_|Ls], Acc) ->
+ partition_dl_1(DLs, Ls, Acc);
+partition_dl_1([], _, Acc) ->
+ {reverse(Acc),[]};
+partition_dl_1([_|_]=DLs, [], Acc) ->
+ {reverse(Acc),DLs}.
+
+filter_deflocs([{Tuple,DefLoc0}|DLs], Blocks) ->
+ %% Input is a list of sinks of get_tuple_element instructions in
+ %% execution order from the same tuple in the same clause.
+ [{_,{_,First}}|_] = DefLoc0,
+ Paths = find_paths_to_check(DefLoc0, First),
+ WillGC0 = ordsets:from_list([FromTo || {{_,_}=FromTo,_} <- Paths]),
+ WillGC1 = [{{From,To},will_gc(From, To, Blocks, true)} ||
+ {From,To} <- WillGC0],
+ WillGC = maps:from_list(WillGC1),
+
+ %% Separate sinks that will force the reference to the tuple to be
+ %% saved on the stack from sinks that don't force.
+ {DefLocGC0,DefLocNoGC} =
+ partition(fun({{_,_}=FromTo,_}) ->
+ map_get(FromTo, WillGC)
+ end, Paths),
+
+ %% Avoid potentially harmful sinks.
+ DefLocGC = filter_gc_deflocs(DefLocGC0, Tuple, First, Blocks),
+
+ %% Construct the complete list of sink operations.
+ DefLoc1 = DefLocGC ++ DefLocNoGC,
+ [DL || {_,{_,{From,To}}=DL} <- DefLoc1, From =/= To] ++
+ filter_deflocs(DLs, Blocks);
+filter_deflocs([], _) -> [].
+
+%% Use an heuristic to avoid harmful sinking in lists:mapfold/3 and
+%% similar functions.
+filter_gc_deflocs(DefLocGC, Tuple, First, Blocks) ->
+ case DefLocGC of
+ [] ->
+ [];
+ [{_,{_,{From,To}}}] ->
+ %% There is only one get_tuple_element instruction that
+ %% can be sunk. That means that we may not gain any slots
+ %% by sinking it.
+ case is_on_stack(First, Tuple, Blocks) of
+ true ->
+ %% The tuple itself must be stored in a stack slot
+ %% (because it will be used later) in addition to
+ %% the tuple element being extracted. It is
+ %% probably a win to sink this instruction.
+ DefLocGC;
+ false ->
+ case will_gc(From, To, Blocks, false) of
+ false ->
+ %% There is no risk for recursive calls,
+ %% so it should be safe to
+ %% sink. Theoretically, we shouldn't win
+ %% any stack slots, but in practice it
+ %% seems that sinking makes it more likely
+ %% that the stack slot for a dying value
+ %% can be immediately reused for another
+ %% value.
+ DefLocGC;
+ true ->
+ %% This function could be involved in a
+ %% recursive call. Since there is no
+ %% obvious reduction in the number of
+ %% stack slots, we will not sink.
+ []
+ end
+ end;
+ [_,_|_] ->
+ %% More than one get_tuple_element instruction can be
+ %% sunk. Sinking will almost certainly reduce the number
+ %% of stack slots.
+ DefLocGC
+ end.
+
+find_paths_to_check([{_,{_,To}}=Move|T], First) ->
+ [{{First,To},Move}|find_paths_to_check(T, First)];
+find_paths_to_check([], _First) -> [].
+
+will_gc(From, To, Blocks, All) ->
+ will_gc(beam_ssa:rpo([From], Blocks), To, Blocks, All, #{From => false}).
+
+will_gc([To|_], To, _Blocks, _All, WillGC) ->
+ map_get(To, WillGC);
+will_gc([L|Ls], To, Blocks, All, WillGC0) ->
+ #b_blk{is=Is} = Blk = map_get(L, Blocks),
+ GC = map_get(L, WillGC0) orelse will_gc_is(Is, All),
+ WillGC = gc_update_successors(Blk, GC, WillGC0),
+ will_gc(Ls, To, Blocks, All, WillGC).
+
+will_gc_is([#b_set{op=call,args=Args}|Is], false) ->
+ case Args of
+ [#b_remote{mod=#b_literal{val=erlang}}|_] ->
+ %% Assume that remote calls to the erlang module can't cause a recursive
+ %% call.
+ will_gc_is(Is, false);
+ [_|_] ->
+ true
+ end;
+will_gc_is([_|Is], false) ->
+ %% Instructions that clobber X registers may cause a GC, but will not cause
+ %% a recursive call.
+ will_gc_is(Is, false);
+will_gc_is([I|Is], All) ->
+ beam_ssa:clobbers_xregs(I) orelse will_gc_is(Is, All);
+will_gc_is([], _All) -> false.
+
+is_on_stack(From, Var, Blocks) ->
+ is_on_stack(beam_ssa:rpo([From], Blocks), Var, Blocks, #{From => false}).
+
+is_on_stack([L|Ls], Var, Blocks, WillGC0) ->
+ #b_blk{is=Is} = Blk = map_get(L, Blocks),
+ GC0 = map_get(L, WillGC0),
+ try is_on_stack_is(Is, Var, GC0) of
+ GC ->
+ WillGC = gc_update_successors(Blk, GC, WillGC0),
+ is_on_stack(Ls, Var, Blocks, WillGC)
+ catch
+ throw:{done,GC} ->
+ GC
+ end;
+is_on_stack([], _Var, _, _) -> false.
+
+is_on_stack_is([#b_set{op=get_tuple_element}|Is], Var, GC) ->
+ is_on_stack_is(Is, Var, GC);
+is_on_stack_is([I|Is], Var, GC0) ->
+ case GC0 andalso member(Var, beam_ssa:used(I)) of
+ true ->
+ throw({done,GC0});
+ false ->
+ GC = GC0 orelse beam_ssa:clobbers_xregs(I),
+ is_on_stack_is(Is, Var, GC)
+ end;
+is_on_stack_is([], _, GC) -> GC.
+
+gc_update_successors(Blk, GC, WillGC) ->
+ foldl(fun(L, Acc) ->
+ case Acc of
+ #{L := true} -> Acc;
+ #{L := false} when GC =:= false -> Acc;
+ #{} -> Acc#{L => GC}
+ end
+ end, WillGC, beam_ssa:successors(Blk)).
+
%% unsuitable(Linear, Blocks) -> Unsuitable.
%% Return an ordset of block labels for the blocks that are not
%% suitable for sinking of get_tuple_element instructions.
@@ -2327,19 +2551,22 @@ is_loop_header(L, Blocks) ->
%% of the variable and as near to the uses of as possible.
new_def_locations([{V,UsedIn}|Vs], Defs, Dom, Numbering, Unsuitable) ->
- DefIn = map_get(V, Defs),
+ {DefIn,Tuple} = map_get(V, Defs),
Common = common_dominator(UsedIn, Dom, Numbering, Unsuitable),
- case member(Common, map_get(DefIn, Dom)) of
- true ->
- %% The common dominator is either DefIn or an
- %% ancestor of DefIn.
- new_def_locations(Vs, Defs, Dom, Numbering, Unsuitable);
- false ->
- %% We have found a suitable descendant of DefIn,
- %% to which the get_tuple_element instruction can
- %% be sunk.
- [{V,Common}|new_def_locations(Vs, Defs, Dom, Numbering, Unsuitable)]
- end;
+ Sink = case member(Common, map_get(DefIn, Dom)) of
+ true ->
+ %% The common dominator is either DefIn or an
+ %% ancestor of DefIn. We'll need to consider all
+ %% get_tuple_element instructions so we will add
+ %% a dummy sink.
+ {V,Tuple,{DefIn,DefIn}};
+ false ->
+ %% We have found a suitable descendant of DefIn,
+ %% to which the get_tuple_element instruction can
+ %% be sunk.
+ {V,Tuple,{DefIn,Common}}
+ end,
+ [Sink|new_def_locations(Vs, Defs, Dom, Numbering, Unsuitable)];
new_def_locations([], _, _, _, _) -> [].
common_dominator(Ls0, Dom, Numbering, Unsuitable) ->
@@ -2362,7 +2589,6 @@ move_defs(V, From, To, Blocks) ->
{Def,FromBlk} = remove_def(V, FromBlk0),
try insert_def(V, Def, ToBlk0) of
ToBlk ->
- %%io:format("~p: ~p => ~p\n", [V,From,To]),
Blocks#{From:=FromBlk,To:=ToBlk}
catch
throw:not_possible ->
@@ -2683,6 +2909,170 @@ unfold_arg(#b_literal{val=Val}=Lit, LitMap, X) ->
unfold_arg(Expr, _LitMap, _X) -> Expr.
%%%
+%%% Optimize tail calls created as the result of optimizations.
+%%%
+%%% Consider the following example of a tail call in Erlang code:
+%%%
+%%% bar() ->
+%%% foo().
+%%%
+%%% The SSA code for the call will look like this:
+%%%
+%%% @ssa_ret = call (`foo`/0)
+%%% ret @ssa_ret
+%%%
+%%% Sometimes optimizations create new tail calls. Consider this
+%%% slight variation of the example:
+%%%
+%%% bar() ->
+%%% {_,_} = foo().
+%%%
+%%% foo() -> {a,b}.
+%%%
+%%% If beam_ssa_type can figure out that `foo/0` always returns a tuple
+%%% of size two, the test for a tuple is no longer needed and the call
+%%% to `foo/0` will become a tail call. However, the resulting SSA
+%%% code will look like this:
+%%%
+%%% @ssa_ret = call (`foo`/0)
+%%% @ssa_bool = succeeded:body @ssa_ret
+%%% br @ssa_bool, ^999, ^1
+%%%
+%%% 999:
+%%% ret @ssa_ret
+%%%
+%%% The beam_ssa_codegen pass will not recognize this code as a tail
+%%% call and will generate an unncessary stack frame. It may also
+%%% generate unecessary `kill` instructions.
+%%%
+%%% To avoid those extra instructions, this optimization will
+%%% eliminate the `succeeded:body` and `br` instructions and insert
+%%% the `ret` in the same block as the call:
+%%%
+%%% @ssa_ret = call (`foo`/0)
+%%% ret @ssa_ret
+%%%
+%%% Finally, consider this example:
+%%%
+%%% bar() ->
+%%% foo_ok(),
+%%% ok.
+%%%
+%%% foo_ok() -> ok.
+%%%
+%%% The SSA code for the call to `foo_ok/0` will look like:
+%%%
+%%% %% Result type: `ok`
+%%% @ssa_ignored = call (`foo_ok`/0)
+%%% @ssa_bool = succeeded:body @ssa_ignored
+%%% br @ssa_bool, ^999, ^1
+%%%
+%%% 999:
+%%% ret `ok`
+%%%
+%%% Since the call to `foo_ok/0` has an annotation indicating that the
+%%% call will always return the atom `ok`, the code can be simplified
+%%% like this:
+%%%
+%%% @ssa_ignored = call (`foo_ok`/0)
+%%% ret @ssa_ignored
+%%%
+%%% The beam_jump pass does the same optimization, but it does it too
+%%% late to avoid creating an uncessary stack frame or unnecessary
+%%% `kill` instructions.
+%%%
+
+ssa_opt_tail_calls({St,FuncDb}) ->
+ #opt_st{ssa=Blocks0} = St,
+ Blocks = opt_tail_calls(beam_ssa:rpo(Blocks0), Blocks0),
+ {St#opt_st{ssa=Blocks},FuncDb}.
+
+opt_tail_calls([L|Ls], Blocks0) ->
+ #b_blk{is=Is0,last=Last} = Blk0 = map_get(L, Blocks0),
+
+ %% Does this block end with a two-way branch whose success
+ %% label targets an empty block with a `ret` terminator?
+ case is_potential_tail_call(Last, Blocks0) of
+ {yes,Bool,Ret} ->
+ %% Yes, `Ret` is the value returned from that block
+ %% (either a variable or literal). Do the instructions
+ %% in this block end with a `call` instruction that
+ %% returns the same value as `Ret`, followed by a
+ %% `succeeded:body` instruction?
+ case is_tail_call_is(Is0, Bool, Ret, []) of
+ {yes,Is,Var} ->
+ %% Yes, this is a tail call. `Is` is the instructions
+ %% in the block with `succeeded:body` removed, and
+ %% `Var` is the destination variable for the return
+ %% value of the call. Rewrite this block to directly
+ %% return `Var`.
+ Blk = Blk0#b_blk{is=Is,last=#b_ret{arg=Var}},
+ Blocks = Blocks0#{L:=Blk},
+ opt_tail_calls(Ls, Blocks);
+ no ->
+ %% No, the block does not end with a call, or the
+ %% the call instruction has not the same value
+ %% as `Ret`.
+ opt_tail_calls(Ls, Blocks0)
+ end;
+ no ->
+ opt_tail_calls(Ls, Blocks0)
+ end;
+opt_tail_calls([], Blocks) -> Blocks.
+
+is_potential_tail_call(#b_br{bool=#b_var{}=Bool,succ=Succ}, Blocks) ->
+ case Blocks of
+ #{Succ := #b_blk{is=[],last=#b_ret{arg=Arg}}} ->
+ %% This could be a tail call.
+ {yes,Bool,Arg};
+ #{} ->
+ %% The block is not empty or does not have a `ret` terminator.
+ no
+ end;
+is_potential_tail_call(_, _) ->
+ %% Not a two-way branch (a `succeeded:body` instruction must be
+ %% followed by a two-way branch).
+ no.
+
+is_tail_call_is([#b_set{op=call,dst=Dst}=Call,
+ #b_set{op={succeeded,body},dst=Bool}],
+ Bool, Ret, Acc) ->
+ IsTailCall =
+ case Ret of
+ #b_literal{val=Val} ->
+ %% The return value for this function is a literal.
+ %% Now test whether it is the same literal that the
+ %% `call` instruction returns.
+ Type = beam_ssa:get_anno(result_type, Call, any),
+ case beam_types:get_singleton_value(Type) of
+ {ok,Val} ->
+ %% Same value.
+ true;
+ {ok,_} ->
+ %% Wrong value.
+ false;
+ error ->
+ %% The type for the return value is not a singleton value.
+ false
+ end;
+ #b_var{} ->
+ %% It is a tail call if the variable set by the `call` instruction
+ %% is the same variable as the argument for the `ret` terminator.
+ Ret =:= Dst
+ end,
+ case IsTailCall of
+ true ->
+ %% Return the instructions in the block with `succeeded:body` removed.
+ Is = reverse(Acc, [Call]),
+ {yes,Is,Dst};
+ false ->
+ no
+ end;
+is_tail_call_is([I|Is], Bool, Ret, Acc) ->
+ is_tail_call_is(Is, Bool, Ret, [I|Acc]);
+is_tail_call_is([], _Bool, _Ret, _Acc) -> no.
+
+%%%
%%% Common utilities.
%%%
diff --git a/lib/compiler/src/beam_trim.erl b/lib/compiler/src/beam_trim.erl
index 09302b6a79..017d2b7d8c 100644
--- a/lib/compiler/src/beam_trim.erl
+++ b/lib/compiler/src/beam_trim.erl
@@ -63,7 +63,8 @@ trim([{kill,_}|_]=Is0, St, Acc) ->
%% Calculate all recipes that are not worse in terms
%% of estimated execution time. The recipes are ordered
%% in descending order from how much they trim.
- Recipes = trim_recipes(Layout),
+ IsNotRecursive = is_not_recursive(Is1),
+ Recipes = trim_recipes(Layout, IsNotRecursive),
%% Try the recipes in order. A recipe may not work out because
%% a register that was previously killed may be
@@ -85,6 +86,21 @@ trim([I|Is], St, Acc) ->
trim([], _, Acc) ->
reverse(Acc).
+%% is_not_recursive([Instruction]) -> true|false.
+%% Test whether the next call or apply instruction may
+%% do a recursive call. Return `true` if the call is
+%% definitely not recursive, and `false` otherwise.
+is_not_recursive([{call_ext,_,Ext}|_]) ->
+ case Ext of
+ {extfunc,M,F,A} ->
+ erl_bifs:is_pure(M, F, A);
+ _ ->
+ false
+ end;
+is_not_recursive([{block,_}|Is]) -> is_not_recursive(Is);
+is_not_recursive([{line,_}|Is]) -> is_not_recursive(Is);
+is_not_recursive(_) -> false.
+
%% trim_recipes([{kill,R}|{live,R}|{dead,R}]) -> [Recipe].
%% Recipe = {Kills,NumberToTrim,Moves}
%% Kills = [{kill,Y}]
@@ -93,34 +109,34 @@ trim([], _, Acc) ->
%% Calculate how to best trim the stack and kill the correct
%% Y registers. Return a list of possible recipes. The best
%% recipe (the one that trims the most) is first in the list.
-%% All of the recipes are no worse in estimated execution time
-%% than the original sequences of kill instructions.
-trim_recipes(Layout) ->
- Cost = length([I || {kill,_}=I <- Layout]),
- trim_recipes_1(Layout, 0, [], {Cost,[]}).
+trim_recipes(Layout, IsNotRecursive) ->
+ Recipes = construct_recipes(Layout, 0, [], []),
+ NumOrigKills = length([I || {kill,_}=I <- Layout]),
+ IsTooExpensive = is_too_expensive_fun(IsNotRecursive),
+ [R || R <- Recipes,
+ not is_too_expensive(R, NumOrigKills, IsTooExpensive)].
-trim_recipes_1([{kill,{y,Trim0}}|Ks], Trim0, Moves, Recipes0) ->
+construct_recipes([{kill,{y,Trim0}}|Ks], Trim0, Moves, Acc) ->
Trim = Trim0 + 1,
- Recipes = save_recipe(Ks, Trim, Moves, Recipes0),
- trim_recipes_1(Ks, Trim, Moves, Recipes);
-trim_recipes_1([{dead,{y,Trim0}}|Ks], Trim0, Moves, Recipes0) ->
+ Recipe = {Ks,Trim,Moves},
+ construct_recipes(Ks, Trim, Moves, [Recipe|Acc]);
+construct_recipes([{dead,{y,Trim0}}|Ks], Trim0, Moves, Acc) ->
Trim = Trim0 + 1,
- Recipes = save_recipe(Ks, Trim, Moves, Recipes0),
- trim_recipes_1(Ks, Trim, Moves, Recipes);
-trim_recipes_1([{live,{y,Trim0}=Src}|Ks0], Trim0, Moves0, Recipes0) ->
+ Recipe = {Ks,Trim,Moves},
+ construct_recipes(Ks, Trim, Moves, [Recipe|Acc]);
+construct_recipes([{live,{y,Trim0}=Src}|Ks0], Trim0, Moves0, Acc) ->
case take_last_dead(Ks0) of
none ->
- {_,RecipesList} = Recipes0,
- RecipesList;
+ %% No more recipes are possible.
+ Acc;
{Dst,Ks} ->
Trim = Trim0 + 1,
Moves = [{move,Src,Dst}|Moves0],
- Recipes = save_recipe(Ks, Trim, Moves, Recipes0),
- trim_recipes_1(Ks, Trim, Moves, Recipes)
+ Recipe = {Ks,Trim,Moves},
+ construct_recipes(Ks, Trim, Moves, [Recipe|Acc])
end;
-trim_recipes_1([], _, _, {_,RecipesList}) ->
- RecipesList.
+construct_recipes([], _, _, Acc) -> Acc.
take_last_dead(L) ->
take_last_dead_1(reverse(L)).
@@ -131,33 +147,47 @@ take_last_dead_1([{dead,Reg}|Is]) ->
{Reg,reverse(Is)};
take_last_dead_1(_) -> none.
-save_recipe(Ks, Trim, Moves, {MaxCost,Acc}=Recipes) ->
- case recipe_cost(Ks, Moves) of
- Cost when Cost =< MaxCost ->
- %% The price is right.
- {MaxCost,[{Ks,Trim,Moves}|Acc]};
- _Cost ->
- %% Too expensive.
- Recipes
+%% Is trimming too expensive?
+is_too_expensive({Ks,_,Moves}, NumOrigKills, IsTooExpensive) ->
+ NumKills = num_kills(Ks, 0),
+ NumMoves = length(Moves),
+ IsTooExpensive(NumKills, NumMoves, NumOrigKills).
+
+num_kills([{kill,_}|T], Acc) ->
+ num_kills(T, Acc+1);
+num_kills([_|T], Acc) ->
+ num_kills(T, Acc);
+num_kills([], Acc) -> Acc.
+
+is_too_expensive_fun(true) ->
+ %% This call is not recursive (because it is a call to a BIF).
+ %% Here we should avoid trimming if the trimming sequence is
+ %% likely to be more expensive than the original sequence.
+ fun(NumKills, NumMoves, NumOrigKills) ->
+ Penalty =
+ if
+ %% Slightly penalize the use of any `move`
+ %% instruction to avoid replacing two `kill`
+ %% instructions with a `move` and a `trim`.
+ NumMoves =/= 0 -> 1;
+ true -> 0
+ end,
+ 1 + Penalty + NumKills + NumMoves > NumOrigKills
+ end;
+is_too_expensive_fun(false) ->
+ %% This call **may** be recursive. In a recursive function that
+ %% builds up a huge stack, having unused stack slots will be very
+ %% expensive. Therefore, we want to be biased towards trimming.
+ %% We will do that by not counting the `trim` instruction in
+ %% the formula below.
+ fun(NumKills, NumMoves, NumOrigKills) ->
+ NumKills + NumMoves > NumOrigKills
end.
-recipe_cost(Ks, Moves) ->
- %% We estimate that a {move,{y,_},{y,_}} instruction is roughly twice as
- %% expensive as a {kill,{y,_}} instruction. A {trim,_} instruction is
- %% roughly as expensive as a {kill,{y,_}} instruction.
-
- recipe_cost_1(Ks, 1+2*length(Moves)).
-
-recipe_cost_1([{kill,_}|Ks], Cost) ->
- recipe_cost_1(Ks, Cost+1);
-recipe_cost_1([_|Ks], Cost) ->
- recipe_cost_1(Ks, Cost);
-recipe_cost_1([], Cost) -> Cost.
-
%% try_remap([Recipe], [Instruction], FrameSize) ->
%% {[Instruction],[TrimInstruction]}.
%% Try to renumber Y registers in the instruction stream. The
-%% first rececipe that works will be used.
+%% first recipe that works will be used.
%%
%% This function will issue a `not_possible` exception if none
%% of the recipes were possible to apply.
diff --git a/lib/compiler/src/compiler.app.src b/lib/compiler/src/compiler.app.src
index 234f0b7780..92e9fa74e5 100644
--- a/lib/compiler/src/compiler.app.src
+++ b/lib/compiler/src/compiler.app.src
@@ -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/lib/compiler/test/beam_ssa_SUITE.erl b/lib/compiler/test/beam_ssa_SUITE.erl
index 03cda0c798..28cc0ae268 100644
--- a/lib/compiler/test/beam_ssa_SUITE.erl
+++ b/lib/compiler/test/beam_ssa_SUITE.erl
@@ -23,13 +23,15 @@
init_per_group/2,end_per_group/2,
calls/1,tuple_matching/1,recv/1,maps/1,
cover_ssa_dead/1,combine_sw/1,share_opt/1,
- beam_ssa_dead_crash/1,stack_init/1,grab_bag/1,
- coverage/1]).
+ beam_ssa_dead_crash/1,stack_init/1,
+ mapfoldl/0,mapfoldl/1,
+ grab_bag/1,coverage/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
- [{group,p}].
+ [mapfoldl,
+ {group,p}].
groups() ->
[{p,test_lib:parallel(),
@@ -702,6 +704,74 @@ stack_init(Key, Map) ->
%% (if the second clause was executed).
id(Res).
+%% Test that compiler "optimizations" don't rewrite mapfold/3 to the
+%% equivalent of slow_mapfoldl/3.
+mapfoldl() ->
+ {N,Size} = mapfoldl_limits(),
+ {Time,_} = timer:tc(fun() ->
+ mapfoldl(fun(Sz, _) ->
+ erlang:garbage_collect(),
+ {Sz,erlang:make_tuple(Sz, a)}
+ end, [], [Size])
+ end),
+ Seconds = 15 + ceil(10 * Time * N / 1_000_000),
+ io:format("~p seconds timetrap\n", [Seconds]),
+ [{timetrap,{seconds,Seconds}}].
+
+mapfoldl(_Config) ->
+ test_mapfoldl_implementations(),
+ F = fun(Sz, _) ->
+ erlang:garbage_collect(),
+ {Sz,erlang:make_tuple(Sz, a)}
+ end,
+ {N,Size} = mapfoldl_limits(),
+ List = lists:duplicate(N, Size),
+ {List,Tuple} = mapfoldl(F, [], List),
+ {List,Tuple} = fast_mapfoldl(F, [], List),
+ Size = tuple_size(Tuple),
+ ok.
+
+mapfoldl_limits() ->
+ {1_000,100_000}.
+
+test_mapfoldl_implementations() ->
+ Seq = lists:seq(1, 10),
+ F = fun(N, Sum) -> {N,Sum+N} end,
+ {Seq,55} = mapfoldl(F, 0, Seq),
+ {Seq,55} = fast_mapfoldl(F, 0, Seq),
+ {Seq,55} = slow_mapfoldl(F, 0, Seq),
+ ok.
+
+mapfoldl(F, Acc0, [Hd|Tail]) ->
+ {R,Acc1} = F(Hd, Acc0),
+ {Rs,Acc2} = mapfoldl(F, Acc1, Tail),
+ {[R|Rs],Acc2};
+mapfoldl(F, Acc, []) when is_function(F, 2) -> {[],Acc}.
+
+%% Here is an illustration of how the compiler used to sink
+%% get_tuple_element instructions in a way that would cause all
+%% versions of the accumulator to be kept until the end. The compiler
+%% now uses a heuristic to only sink get_tuple_element instructions if
+%% that would cause fewer values to be saved in the stack frame.
+slow_mapfoldl(F, Acc0, [Hd|Tail]) ->
+ Res1 = F(Hd, Acc0),
+ %% By saving the Res1 tuple, all intermediate accumulators will be
+ %% kept to the end.
+ Res2 = slow_mapfoldl(F, element(2, Res1), Tail),
+ {[element(1, Res1)|element(1, Res2)],element(2, Res2)};
+slow_mapfoldl(F, Acc, []) when is_function(F, 2) -> {[],Acc}.
+
+%% Here is an illustration how the compiler should compile mapfoldl/3
+%% to avoid keeping all intermediate accumulators. Note that
+%% slow_mapfoldl/3 and fast_mapfoldl/3 use the same amount of stack
+%% space.
+fast_mapfoldl(F, Acc0, [Hd|Tail]) ->
+ Res1 = F(Hd, Acc0),
+ R = element(1, Res1),
+ Res2 = fast_mapfoldl(F, element(2, Res1), Tail),
+ {[R|element(1, Res2)],element(2, Res2)};
+fast_mapfoldl(F, Acc, []) when is_function(F, 2) -> {[],Acc}.
+
grab_bag(_Config) ->
{'EXIT',_} = (catch grab_bag_1()),
{'EXIT',_} = (catch grab_bag_2()),
diff --git a/lib/compiler/test/guard_SUITE.erl b/lib/compiler/test/guard_SUITE.erl
index 4a53698e15..0558b8f300 100644
--- a/lib/compiler/test/guard_SUITE.erl
+++ b/lib/compiler/test/guard_SUITE.erl
@@ -2300,6 +2300,8 @@ beam_bool_SUITE(_Config) ->
in_catch(),
recv_semi(),
andalso_repeated_var(),
+ erl1246(),
+ erl1253(),
ok.
before_and_inside_if() ->
@@ -2597,6 +2599,170 @@ andalso_repeated_var() ->
andalso_repeated_var(B) when B andalso B -> ok;
andalso_repeated_var(_) -> error.
+-record(erl1246, {tran_stat = 0}).
+
+erl1246() ->
+ false = erl1246(#erl1246{tran_stat = 0}, #{cid => 1131}),
+ false = erl1246(#erl1246{tran_stat = 12}, #{cid => 1131}),
+ false = erl1246(#erl1246{tran_stat = 12}, #{cid => 9502}),
+ true = erl1246(#erl1246{tran_stat = 0}, #{cid => 9502}),
+ ok.
+
+erl1246(Rec, #{cid := CollID}) ->
+ {GiftCollID, _} = erl1246_conf(gift_coll),
+ IsTranStat = Rec#erl1246.tran_stat =:= erl1246_conf(transform_id),
+ if
+ %% Optimization of 'not' in a guard was broken.
+ CollID =:= GiftCollID andalso not IsTranStat ->
+ true;
+ true ->
+ false
+ end.
+
+erl1246_conf(gift_coll) -> {9502, {112, 45}};
+erl1246_conf(transform_id) -> 12;
+erl1246_conf(_) -> undefined.
+
+erl1253() ->
+ ok = erl1253_orelse_false(a, a, any),
+ ok = erl1253_orelse_false(a, a, true),
+ ok = erl1253_orelse_false(a, a, false),
+ error = erl1253_orelse_false(a, b, any),
+ error = erl1253_orelse_false(a, b, true),
+ ok = erl1253_orelse_false(a, b, false),
+
+ ok = erl1253_orelse_true(a, a, any),
+ ok = erl1253_orelse_true(a, a, true),
+ ok = erl1253_orelse_true(a, a, false),
+ error = erl1253_orelse_true(a, b, any),
+ ok = erl1253_orelse_true(a, b, true),
+ error = erl1253_orelse_true(a, b, false),
+
+ error = erl1253_andalso_false(a, a, any),
+ error = erl1253_andalso_false(a, a, true),
+ ok = erl1253_andalso_false(a, a, false),
+ error = erl1253_andalso_false(a, b, any),
+ error = erl1253_andalso_false(a, b, true),
+ error = erl1253_andalso_false(a, b, false),
+
+ error = erl1253_andalso_true(a, a, any),
+ ok = erl1253_andalso_true(a, a, true),
+ error = erl1253_andalso_true(a, a, false),
+ error = erl1253_andalso_true(a, b, any),
+ error = erl1253_andalso_true(a, b, true),
+ error = erl1253_andalso_true(a, b, false),
+
+ ok.
+
+erl1253_orelse_false(X, Y, Z) ->
+ Res = erl1253_orelse_false_1(X, Y, Z),
+ Res = erl1253_orelse_false_2(X, Y, Z),
+ Res = erl1253_orelse_false_3(X, Y, Z).
+
+erl1253_orelse_false_1(X, Y, Z) ->
+ Bool = Z =:= false,
+ if
+ X =:= Y orelse Bool -> ok;
+ true -> error
+ end.
+
+erl1253_orelse_false_2(X, Y, Z) ->
+ Bool = Z =:= false,
+ if
+ Bool orelse X =:= Y -> ok;
+ true -> error
+ end.
+
+erl1253_orelse_false_3(X, Y, Z) ->
+ Bool1 = X =:= Y,
+ Bool2 = Z =:= false,
+ if
+ Bool1 orelse Bool2 -> ok;
+ true -> error
+ end.
+
+erl1253_orelse_true(X, Y, Z) ->
+ Res = erl1253_orelse_true_1(X, Y, Z),
+ Res = erl1253_orelse_true_2(X, Y, Z),
+ Res = erl1253_orelse_true_3(X, Y, Z).
+
+erl1253_orelse_true_1(X, Y, Z) ->
+ Bool = Z =:= true,
+ if
+ X =:= Y orelse Bool -> ok;
+ true -> error
+ end.
+
+erl1253_orelse_true_2(X, Y, Z) ->
+ Bool = Z =:= true,
+ if
+ Bool orelse X =:= Y -> ok;
+ true -> error
+ end.
+
+erl1253_orelse_true_3(X, Y, Z) ->
+ Bool1 = X =:= Y,
+ Bool2 = Z =:= true,
+ if
+ Bool1 orelse Bool2 -> ok;
+ true -> error
+ end.
+
+erl1253_andalso_false(X, Y, Z) ->
+ Res = erl1253_andalso_false_1(X, Y, Z),
+ Res = erl1253_andalso_false_2(X, Y, Z),
+ Res = erl1253_andalso_false_3(X, Y, Z).
+
+erl1253_andalso_false_1(X, Y, Z) ->
+ Bool = Z =:= false,
+ if
+ X =:= Y andalso Bool -> ok;
+ true -> error
+ end.
+
+erl1253_andalso_false_2(X, Y, Z) ->
+ Bool1 = X =:= Y,
+ Bool2 = Z =:= false,
+ if
+ Bool1 andalso Bool2 -> ok;
+ true -> error
+ end.
+
+erl1253_andalso_false_3(X, Y, Z) ->
+ Bool1 = X =:= Y,
+ Bool2 = Z =:= false,
+ if
+ Bool1 andalso Bool2 -> ok;
+ true -> error
+ end.
+
+erl1253_andalso_true(X, Y, Z) ->
+ Res = erl1253_andalso_true_1(X, Y, Z),
+ Res = erl1253_andalso_true_2(X, Y, Z),
+ Res = erl1253_andalso_true_3(X, Y, Z).
+
+erl1253_andalso_true_1(X, Y, Z) ->
+ Bool = Z =:= true,
+ if
+ X =:= Y andalso Bool -> ok;
+ true -> error
+ end.
+
+erl1253_andalso_true_2(X, Y, Z) ->
+ Bool = Z =:= true,
+ if
+ Bool andalso X =:= Y-> ok;
+ true -> error
+ end.
+
+erl1253_andalso_true_3(X, Y, Z) ->
+ Bool1 = X =:= Y,
+ Bool2 = Z =:= true,
+ if
+ Bool1 andalso Bool2 -> ok;
+ true -> error
+ end.
+
%%%
%%% End of beam_bool_SUITE tests.
%%%
diff --git a/lib/compiler/vsn.mk b/lib/compiler/vsn.mk
index 87d821d7e8..e167c63a7d 100644
--- a/lib/compiler/vsn.mk
+++ b/lib/compiler/vsn.mk
@@ -1 +1 @@
-COMPILER_VSN = 7.5.4
+COMPILER_VSN = 7.6.1
diff --git a/lib/crypto/doc/src/notes.xml b/lib/crypto/doc/src/notes.xml
index b22b46d5e5..5903226f6e 100644
--- a/lib/crypto/doc/src/notes.xml
+++ b/lib/crypto/doc/src/notes.xml
@@ -31,6 +31,93 @@
</header>
<p>This document describes the changes made to the Crypto application.</p>
+<section><title>Crypto 4.7</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Crypto reported unsupported elliptic curves as supported
+ on e.g Fedora distros.</p>
+ <p>
+ Own Id: OTP-16579 Aux Id: ERL-825 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Support for ed25519 and ed448 added to
+ <c>crypto:generate_key</c>.</p>
+ <p>
+ Own Id: OTP-15967 Aux Id: PR-2329 </p>
+ </item>
+ <item>
+ <p>
+ The <seeguide marker="crypto:new_api#the-new-api">new
+ crypto functions api</seeguide> (crypto_init,
+ crypto_update and crypto_one_time) has been updated.</p>
+ <p>
+ There is now a function <seemfa
+ marker="crypto:crypto#crypto_final/1"><c>crypto_final/1</c></seemfa>
+ and a possibility to set options in <seemfa
+ marker="crypto:crypto#crypto_init/3"><c>crypto_init/3</c></seemfa>
+ and <seemfa
+ marker="crypto:crypto#crypto_init/4"><c>crypto_init/4</c></seemfa>.
+ See the manual for details.</p>
+ <p>
+ Own Id: OTP-16160</p>
+ </item>
+ <item>
+ <p>
+ As <seeguide
+ marker="crypto:notes#crypto-4.5">announced</seeguide> in
+ OTP 22.0, a New API was introduced in CRYPTO. See the
+ <seeguide marker="crypto:new_api"><i>New and Old
+ API</i></seeguide> chapter in the CRYPTO User's Guide for
+ more information and suggested replacement functions.</p>
+ <p>
+ <seeguide marker="crypto:new_api#the-old-api">The Old
+ API</seeguide> is now deprecated in OTP-23.0 and will be
+ removed in OTP-24.0.</p>
+ <p>
+ This deprecation includes cipher names. See the section
+ <seeguide
+ marker="crypto:new_api#retired-cipher-names">Retired
+ cipher names</seeguide> in the crypto User's Guide,
+ chapter <seeguide marker="crypto:new_api#the-old-api">The
+ Old API</seeguide>.</p>
+ <p>
+ Own Id: OTP-16232</p>
+ </item>
+ <item>
+ <p>
+ Fix C-compilation without deprecated OpenSSL cryptolib
+ APIs</p>
+ <p>
+ Own Id: OTP-16369 Aux Id: PR-2474 </p>
+ </item>
+ <item>
+ <p>Refactored the internal handling of deprecated and
+ removed functions.</p>
+ <p>
+ Own Id: OTP-16469</p>
+ </item>
+ <item>
+ <p>
+ Added missing 'eddh' to <seemfa
+ marker="crypto:crypto#supports/1">crypto:supports(public_keys)</seemfa>.</p>
+ <p>
+ Own Id: OTP-16583</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Crypto 4.6.5</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/crypto/test/crypto_SUITE.erl b/lib/crypto/test/crypto_SUITE.erl
index ca72601bef..df830b32f6 100644
--- a/lib/crypto/test/crypto_SUITE.erl
+++ b/lib/crypto/test/crypto_SUITE.erl
@@ -135,6 +135,7 @@ groups() ->
{group, dh},
{group, ecdh},
+ {group, eddh},
{group, srp},
{group, chacha20_poly1305},
@@ -217,7 +218,7 @@ groups() ->
{dss, [], [sign_verify
%% Does not work yet: ,public_encrypt, private_encrypt
]},
- {ecdsa, [], [sign_verify
+ {ecdsa, [], [sign_verify, use_all_ec_sign_verify
%% Does not work yet: ,public_encrypt, private_encrypt
]},
{ed25519, [], [sign_verify,
@@ -229,7 +230,8 @@ groups() ->
generate
]},
{dh, [], [generate_compute, compute_bug]},
- {ecdh, [], [use_all_elliptic_curves, compute, generate]},
+ {ecdh, [], [compute, generate, use_all_ecdh_generate_compute]},
+ {eddh, [], [compute, generate, use_all_eddh_generate_compute]},
{srp, [], [generate_compute]},
{des_cbc, [], [block, api_ng, api_ng_one_shot, api_ng_tls]},
{des_cfb, [], [block, api_ng, api_ng_one_shot, api_ng_tls]},
@@ -330,7 +332,7 @@ init_per_suite(Config) ->
{ok, _} = zip:unzip("cmactestvectors.zip"),
{ok, _} = zip:unzip("gcmtestvectors.zip"),
- try crypto:start() of
+ try is_ok(crypto:start()) of
ok ->
catch ct:comment("~s",[element(3,hd(crypto:info_lib()))]),
catch ct:log("crypto:info_lib() -> ~p~n"
@@ -353,10 +355,18 @@ init_per_suite(Config) ->
crypto:rand_seed(<< <<Bin/binary>> || _ <- lists:seq(1,16) >>),
Config
end
- catch _:_ ->
+
+ catch C:E:S ->
+ ct:log("~p ~p~n~p", [C,E,S]),
{fail, "Crypto did not start"}
end.
+is_ok(ok) -> ok;
+is_ok({error, already_started}) -> ok;
+is_ok({error,{already_started,crypto}}) -> ok.
+
+
+
end_per_suite(_Config) ->
application:stop(crypto).
@@ -471,8 +481,7 @@ hmac() ->
[{doc, "Test hmac function"}].
hmac(Config) when is_list(Config) ->
Tuples = lazy_eval(proplists:get_value(hmac, Config)),
- lists:foreach(fun hmac_check/1, Tuples),
- lists:foreach(fun hmac_check/1, mac_listify(Tuples)).
+ do_cipher_tests(fun hmac_check/1, Tuples++mac_listify(Tuples)).
%%--------------------------------------------------------------------
no_hmac() ->
@@ -500,8 +509,7 @@ cmac() ->
[{doc, "Test all different cmac functions"}].
cmac(Config) when is_list(Config) ->
Pairs = lazy_eval(proplists:get_value(cmac, Config)),
- lists:foreach(fun cmac_check/1, Pairs),
- lists:foreach(fun cmac_check/1, mac_listify(Pairs)).
+ do_cipher_tests(fun cmac_check/1, Pairs ++ mac_listify(Pairs)).
%%--------------------------------------------------------------------
poly1305() ->
@@ -531,8 +539,7 @@ block() ->
[{doc, "Test block ciphers"}].
block(Config) when is_list(Config) ->
[_|_] = Blocks = lazy_eval(proplists:get_value(cipher, Config)),
- lists:foreach(fun block_cipher/1, Blocks),
- lists:foreach(fun block_cipher/1, block_iolistify(Blocks)),
+ do_cipher_tests(fun block_cipher/1, Blocks++block_iolistify(Blocks)),
lists:foreach(fun block_cipher_increment/1, block_iolistify(Blocks)).
%%--------------------------------------------------------------------
@@ -604,7 +611,7 @@ api_ng_cipher_increment_loop(Ref, InTexts) ->
Bin
catch
error:Error ->
- ct:pal("Txt = ~p",[Txt]),
+ ct:log("Txt = ~p",[Txt]),
ct:fail("~p",[Error])
end
end, InTexts).
@@ -676,17 +683,16 @@ api_ng_tls(Config) when is_list(Config) ->
lists:foreach(fun do_api_ng_tls/1, Ciphers).
-do_api_ng_tls({Type, Key, PlainTexts}=_X) ->
- ct:log("~p",[_X]),
+do_api_ng_tls({Type, Key, PlainTexts}) ->
do_api_ng_tls({Type, Key, <<>>, PlainTexts});
-do_api_ng_tls({Type, Key, IV, PlainTexts}=_X) ->
- ct:log("~p",[_X]),
+do_api_ng_tls({Type, Key, IV, PlainTexts}) ->
do_api_ng_tls({Type, Key, IV, PlainTexts, undefined});
-do_api_ng_tls({Type, Key, IV, PlainText0, ExpectedEncText}=_X) ->
- ct:log("~p",[_X]),
+do_api_ng_tls({Type, Key, IV, PlainText0, ExpectedEncText}) ->
PlainText = iolist_to_binary(lazy_eval(PlainText0)),
+ ct:log("Type = ~p~nKey = ~p~nIV = ~p~nPlainText = ~p~nExpectedEncText = ~p",
+ [Type, Key, IV, PlainText, ExpectedEncText]),
Renc = crypto:crypto_dyn_iv_init(Type, Key, true),
Rdec = crypto:crypto_dyn_iv_init(Type, Key, false),
EncTxt = crypto:crypto_dyn_iv_update(Renc, PlainText, IV),
@@ -832,8 +838,6 @@ no_stream_ivec(Config) when is_list(Config) ->
notsup(fun crypto:stream_init/3, [Type, <<"Key">>, <<"Ivec">>]).
%%--------------------------------------------------------------------
-aead() ->
- [{doc, "Test AEAD ciphers"}].
aead(Config) when is_list(Config) ->
[_|_] = AEADs = lazy_eval(proplists:get_value(cipher, Config)),
FilteredAEADs =
@@ -848,7 +852,7 @@ aead(Config) when is_list(Config) ->
IVLen >= 12
end, AEADs)
end,
- lists:foreach(fun aead_cipher/1, FilteredAEADs).
+ do_cipher_tests(fun aead_cipher/1, FilteredAEADs).
%%--------------------------------------------------------------------
aead_ng(Config) when is_list(Config) ->
@@ -865,7 +869,7 @@ aead_ng(Config) when is_list(Config) ->
IVLen >= 12
end, AEADs)
end,
- lists:foreach(fun aead_cipher_ng/1, FilteredAEADs ++ spec_0_bytes(Config)).
+ do_cipher_tests(fun aead_cipher_ng/1, FilteredAEADs ++ spec_0_bytes(Config)).
%%--------------------------------------------------------------------
aead_bad_tag(Config) ->
@@ -882,7 +886,7 @@ aead_bad_tag(Config) ->
IVLen >= 12
end, AEADs)
end,
- lists:foreach(fun aead_cipher_bad_tag/1, FilteredAEADs).
+ do_cipher_tests(fun aead_cipher_bad_tag/1, FilteredAEADs).
%%--------------------------------------------------------------------
sign_verify() ->
@@ -902,6 +906,7 @@ no_sign_verify(Config) when is_list(Config) ->
public_encrypt() ->
[{doc, "Test public_encrypt/decrypt "}].
public_encrypt(Config) when is_list(Config) ->
+ ct:log("public_encrypt", []),
Params = proplists:get_value(pub_pub_encrypt, Config, []),
lists:foreach(fun do_public_encrypt/1, Params).
@@ -961,9 +966,7 @@ compute(Config) when is_list(Config) ->
Gen = proplists:get_value(compute, Config),
lists:foreach(fun do_compute/1, Gen).
%%--------------------------------------------------------------------
-use_all_elliptic_curves() ->
- [{doc, " Test that all curves from crypto:ec_curves/0"}].
-use_all_elliptic_curves(_Config) ->
+use_all_ec_sign_verify(_Config) ->
Msg = <<"hello world!">>,
Sups = crypto:supports(),
Curves = proplists:get_value(curves, Sups),
@@ -975,6 +978,7 @@ use_all_elliptic_curves(_Config) ->
Results =
[{{Curve,Hash},
try
+ ct:log("~p ~p",[Curve,Hash]),
{Pub,Priv} = crypto:generate_key(ecdh, Curve),
true = is_binary(Pub),
true = is_binary(Priv),
@@ -1000,6 +1004,57 @@ use_all_elliptic_curves(_Config) ->
end.
%%--------------------------------------------------------------------
+use_all_ecdh_generate_compute(Config) ->
+ Curves = crypto:supports(curves) -- [ed25519, ed448, x25519, x448],
+ do_dh_curves(Config, Curves).
+
+use_all_eddh_generate_compute(Config) ->
+ AllCurves = crypto:supports(curves),
+ Curves = [C || C <- [x25519, x448],
+ lists:member(C, AllCurves)],
+ do_dh_curves(Config, Curves).
+
+do_dh_curves(_Config, Curves) ->
+ ct:log("Lib: ~p~nFIPS: ~p~nCurves:~n~p~n", [crypto:info_lib(),
+ crypto:info_fips(),
+ Curves]),
+ Results =
+ [{Curve,
+ try
+ ct:log("~p",[Curve]),
+ {APub,APriv} = crypto:generate_key(ecdh, Curve),
+ {BPub,BPriv} = crypto:generate_key(ecdh, Curve),
+ true = is_binary(APub),
+ true = is_binary(APriv),
+ true = is_binary(BPub),
+ true = is_binary(BPriv),
+
+ ACommonSecret = crypto:compute_key(ecdh, BPub, APriv, Curve),
+ BCommonSecret = crypto:compute_key(ecdh, APub, BPriv, Curve),
+ ACommonSecret == BCommonSecret
+ catch
+ C:E ->
+ {C,E}
+ end}
+ || Curve <- Curves
+ ],
+
+ Fails =
+ lists:filter(fun({_,true}) -> false;
+ (_) -> true
+ end, Results),
+
+ case Fails of
+ [] ->
+ ct:comment("All ~p passed",[length(Results)]),
+ ok;
+ _ ->
+ ct:comment("passed: ~p, failed: ~p",[length(Results),length(Fails)]),
+ ct:log("Fails:~n~p",[Fails]),
+ ct:fail("Bad curve(s)",[])
+ end.
+
+%%--------------------------------------------------------------------
generate() ->
[{doc, " Test crypto:generate_key"}].
generate(Config) when is_list(Config) ->
@@ -1065,13 +1120,14 @@ cipher_info(Config) when is_list(Config) ->
of
_ -> Ok
catch Cls:Exc ->
- ct:pal("~p:~p ~p",[Cls,Exc,C]),
+ ct:log("~p:~p ~p",[Cls,Exc,C]),
false
end
end,
true,
-crypto:supports(ciphers)) of
-%% proplists:get_value(ciphers, crypto:supports())) of
+ crypto:supports(ciphers)
+ )
+ of
true ->
ok;
false ->
@@ -1175,76 +1231,49 @@ hmac_increment(State0, [Increment | Rest]) ->
hmac_increment(State, Rest).
%%%----------------------------------------------------------------
-cmac_check({cmac, Type, Key, Text, CMac}) ->
+cmac_check({cmac, Type, Key, Text, CMac}=T) ->
ExpCMac = iolist_to_binary(CMac),
- case crypto:cmac(Type, Key, Text) of
- ExpCMac ->
- ok;
- Other ->
- ct:fail({{crypto, cmac, [Type, Key, Text]}, {expected, ExpCMac}, {got, Other}})
- end;
-cmac_check({cmac, Type, Key, Text, Size, CMac}) ->
+ cipher_test(T,
+ fun() -> crypto:cmac(Type, Key, Text) end,
+ ExpCMac);
+cmac_check({cmac, Type, Key, Text, Size, CMac}=T) ->
ExpCMac = iolist_to_binary(CMac),
- case crypto:cmac(Type, Key, Text, Size) of
- ExpCMac ->
- ok;
- Other ->
- ct:fail({{crypto, cmac, [Type, Key, Text, Size]}, {expected, ExpCMac}, {got, Other}})
- end.
-
+ cipher_test(T,
+ fun() -> crypto:cmac(Type, Key, Text, Size) end,
+ ExpCMac).
-mac_check({MacType, SubType, Key, Text, Mac}) ->
+mac_check({MacType, SubType, Key, Text, Mac}=T) ->
ExpMac = iolist_to_binary(Mac),
- case crypto:mac(MacType, SubType, Key, Text) of
- ExpMac ->
- ok;
- Other ->
- ct:fail({{crypto, mac, [MacType, SubType, Key, Text]}, {expected, ExpMac}, {got, Other}})
- end;
-mac_check({MacType, SubType, Key, Text, Size, Mac}) ->
+ cipher_test(T,
+ fun() -> crypto:mac(MacType, SubType, Key, Text) end,
+ ExpMac);
+mac_check({MacType, SubType, Key, Text, Size, Mac}=T) ->
ExpMac = iolist_to_binary(Mac),
- case crypto:mac(MacType, SubType, Key, Text, Size) of
- ExpMac ->
- ok;
- Other ->
- ct:fail({{crypto, mac, [MacType, SubType, Key, Text]}, {expected, ExpMac}, {got, Other}})
- end.
+ cipher_test(T,
+ fun() -> crypto:mac(MacType, SubType, Key, Text, Size) end,
+ ExpMac).
-
-block_cipher({Type, Key, PlainText}) ->
+block_cipher({Type, Key, PlainText}=T) ->
Plain = iolist_to_binary(PlainText),
CipherText = crypto:block_encrypt(Type, Key, PlainText),
- case crypto:block_decrypt(Type, Key, CipherText) of
- Plain ->
- ok;
- Other ->
- ct:fail({{crypto, block_decrypt, [Type, Key, CipherText]}, {expected, Plain}, {got, Other}})
- end;
+ cipher_test(T,
+ fun() -> crypto:block_decrypt(Type, Key, CipherText) end,
+ Plain);
-block_cipher({Type, Key, IV, PlainText}) ->
+block_cipher({Type, Key, IV, PlainText}=T) ->
Plain = iolist_to_binary(PlainText),
CipherText = crypto:block_encrypt(Type, Key, IV, PlainText),
- case crypto:block_decrypt(Type, Key, IV, CipherText) of
- Plain ->
- ok;
- Other ->
- ct:fail({{crypto, block_decrypt, [Type, Key, IV, CipherText]}, {expected, Plain}, {got, Other}})
- end;
+ cipher_test(T,
+ fun() -> crypto:block_decrypt(Type, Key, IV, CipherText) end,
+ Plain);
-block_cipher({Type, Key, IV, PlainText, CipherText}) ->
+block_cipher({Type, Key, IV, PlainText, CipherText}=T) ->
Plain = iolist_to_binary(PlainText),
- case crypto:block_encrypt(Type, Key, IV, Plain) of
- CipherText ->
- ok;
- Other0 ->
- ct:fail({{crypto, block_encrypt, [Type, Key, IV, Plain]}, {expected, CipherText}, {got, Other0}})
- end,
- case crypto:block_decrypt(Type, Key, IV, CipherText) of
- Plain ->
- ok;
- Other1 ->
- ct:fail({{crypto, block_decrypt, [Type, Key, IV, CipherText]}, {expected, Plain}, {got, Other1}})
- end.
+ cipher_test(T,
+ fun() -> crypto:block_encrypt(Type, Key, IV, Plain) end,
+ CipherText,
+ fun() -> crypto:block_decrypt(Type, Key, IV, CipherText) end,
+ Plain).
block_cipher_increment({Type, Key, IV, PlainTexts}) when Type == des_cbc ;
Type == des3_cbc ;
@@ -1365,124 +1394,99 @@ stream_cipher_incment_loop(State0, OrigState, [PlainText | PlainTexts], Acc, Pla
{State, CipherText} = crypto:stream_encrypt(State0, PlainText),
stream_cipher_incment_loop(State, OrigState, PlainTexts, [CipherText | Acc], Plain).
-aead_cipher({Type, Key, PlainText, IV, AAD, CipherText, CipherTag, Info}) ->
+aead_cipher({Type, Key, PlainText, IV, AAD, CipherText, CipherTag, _Info}=T) ->
Plain = iolist_to_binary(PlainText),
- case crypto:block_encrypt(Type, Key, IV, {AAD, Plain}) of
- {CipherText, CipherTag} ->
- ok;
- Other0 ->
- ct:fail({{crypto,
- block_encrypt,
- [{info,Info}, {key,Key}, {pt,PlainText}, {iv,IV}, {aad,AAD}, {ct,CipherText}, {tag,CipherTag}]},
- {expected, {CipherText, CipherTag}},
- {got, Other0}})
- end,
- case crypto:block_decrypt(Type, Key, IV, {AAD, CipherText, CipherTag}) of
- Plain ->
- ok;
- Other1 ->
- ct:fail({{crypto,
- block_decrypt,
- [{info,Info}, {key,Key}, {pt,PlainText}, {iv,IV}, {aad,AAD}, {ct,CipherText}, {tag,CipherTag}]},
- {expected, Plain},
- {got, Other1}})
- end;
-aead_cipher({Type, Key, PlainText, IV, AAD, CipherText, CipherTag, TagLen, Info}) ->
+ cipher_test(T,
+ fun() -> crypto:block_encrypt(Type, Key, IV, {AAD, Plain}) end,
+ {CipherText, CipherTag},
+ fun() -> crypto:block_decrypt(Type, Key, IV, {AAD, CipherText, CipherTag}) end,
+ Plain);
+aead_cipher({Type, Key, PlainText, IV, AAD, CipherText, CipherTag, TagLen, _Info}=T) ->
<<TruncatedCipherTag:TagLen/binary, _/binary>> = CipherTag,
Plain = iolist_to_binary(PlainText),
- try crypto:block_encrypt(Type, Key, IV, {AAD, Plain, TagLen}) of
- {CipherText, TruncatedCipherTag} ->
- ok;
- Other0 ->
- ct:fail({{crypto,
- block_encrypt,
- [{info,Info}, {key,Key}, {pt,PlainText}, {iv,IV}, {aad,AAD}, {ct,CipherText}, {tag,CipherTag}, {taglen,TagLen}]},
- {expected, {CipherText, TruncatedCipherTag}},
- {got, Other0}})
- catch
- error:E ->
- ct:log("~p",[{Type, Key, PlainText, IV, AAD, CipherText, CipherTag, TagLen, Info}]),
- try crypto:crypto_one_time_aead(Type, Key, IV, PlainText, AAD, TagLen, true)
- of
- RR ->
- ct:log("Works: ~p",[RR])
- catch
- CC:EE ->
- ct:log("~p:~p", [CC,EE])
- end,
- ct:fail("~p",[E])
- end,
- case crypto:block_decrypt(Type, Key, IV, {AAD, CipherText, TruncatedCipherTag}) of
- Plain ->
- ok;
- Other1 ->
- ct:fail({{crypto,
- block_decrypt,
- [{info,Info}, {key,Key}, {pt,PlainText}, {iv,IV}, {aad,AAD}, {ct,CipherText}, {tag,CipherTag},
- {truncated,TruncatedCipherTag}]},
- {expected, Plain},
- {got, Other1}})
- end.
+ cipher_test(T,
+ fun() -> crypto:block_encrypt(Type, Key, IV, {AAD, Plain, TagLen}) end,
+ {CipherText, TruncatedCipherTag},
+ fun() -> crypto:block_decrypt(Type, Key, IV, {AAD, CipherText, TruncatedCipherTag}) end,
+ Plain).
-aead_cipher_ng({Type, Key, PlainText, IV, AAD, CipherText, CipherTag, Info}) ->
+aead_cipher_ng({Type, Key, PlainText, IV, AAD, CipherText, CipherTag, _Info}=T) ->
Plain = iolist_to_binary(PlainText),
- case crypto:crypto_one_time_aead(Type, Key, IV, PlainText, AAD, true) of
- {CipherText, CipherTag} ->
- ok;
- Other0 ->
- ct:fail({{crypto,
- block_encrypt,
- [{info,Info}, {key,Key}, {pt,PlainText}, {iv,IV}, {aad,AAD}, {ct,CipherText}, {tag,CipherTag}]},
- {expected, {CipherText, CipherTag}},
- {got, Other0}})
- end,
- case crypto:crypto_one_time_aead(Type, Key, IV, CipherText, AAD, CipherTag, false) of
- Plain ->
- ok;
- Other1 ->
- ct:fail({{crypto,
- block_decrypt,
- [{info,Info}, {key,Key}, {pt,PlainText}, {iv,IV}, {aad,AAD}, {ct,CipherText}, {tag,CipherTag}]},
- {expected, Plain},
- {got, Other1}})
- end;
-aead_cipher_ng({Type, Key, PlainText, IV, AAD, CipherText, CipherTag, TagLen, Info}) ->
+ cipher_test(T,
+ fun() -> crypto:crypto_one_time_aead(Type, Key, IV, PlainText, AAD, true) end,
+ {CipherText, CipherTag},
+ fun() -> crypto:crypto_one_time_aead(Type, Key, IV, CipherText, AAD, CipherTag, false) end,
+ Plain);
+aead_cipher_ng({Type, Key, PlainText, IV, AAD, CipherText, CipherTag, TagLen, _Info}=T) ->
<<TruncatedCipherTag:TagLen/binary, _/binary>> = CipherTag,
Plain = iolist_to_binary(PlainText),
- try crypto:crypto_one_time_aead(Type, Key, IV, PlainText, AAD, TagLen, true) of
- {CipherText, TruncatedCipherTag} ->
- ok;
- Other0 ->
- ct:fail({{crypto,
- block_encrypt,
- [{info,Info}, {key,Key}, {pt,PlainText}, {iv,IV}, {aad,AAD}, {ct,CipherText}, {tag,CipherTag}, {taglen,TagLen}]},
- {expected, {CipherText, TruncatedCipherTag}},
- {got, Other0}})
+ cipher_test(T,
+ fun() -> crypto:crypto_one_time_aead(Type, Key, IV, PlainText, AAD, TagLen, true) end,
+ {CipherText, TruncatedCipherTag},
+ fun() -> crypto:crypto_one_time_aead(Type, Key, IV, CipherText, AAD, TruncatedCipherTag, false) end,
+ Plain).
+
+aead_cipher_bad_tag({Type, Key, _PlainText, IV, AAD, CipherText, CipherTag, _Info}=T) ->
+ BadTag = mk_bad_tag(CipherTag),
+ cipher_test(T,
+ fun() -> crypto:crypto_one_time_aead(Type, Key, IV, CipherText, AAD, BadTag, false) end,
+ error);
+aead_cipher_bad_tag({Type, Key, _PlainText, IV, AAD, CipherText, CipherTag, TagLen, _Info}=T) ->
+ <<TruncatedCipherTag:TagLen/binary, _/binary>> = CipherTag,
+ BadTruncatedTag = mk_bad_tag(TruncatedCipherTag),
+ cipher_test(T,
+ fun() -> crypto:crypto_one_time_aead(Type, Key, IV, CipherText, AAD, BadTruncatedTag, false) end,
+ error).
+
+
+cipher_test(T, Fe, Ee, Fd, Ed) ->
+ %% Test encrypt
+ Re = cipher_test(encrypt, T, Fe, Ee),
+ %% Test decrypt
+ Rd = cipher_test(decrypt, T, Fd, Ed),
+ case {Re, Rd} of
+ {ok,ok} -> ok;
+ {ok,_} -> Rd;
+ {_,ok} -> Re;
+ _ -> {Re,Rd}
+ end.
+
+cipher_test(T, F, E) ->
+ cipher_test(notag, T, F, E).
+
+cipher_test(Tag, T, F, E) ->
+ try F() of
+ E -> ok;
+ Other -> {other, {Tag,T,Other}}
catch
- error:E ->
- ct:log("~p",[{Type, Key, PlainText, IV, AAD, CipherText, CipherTag, TagLen, Info}]),
- try crypto:crypto_one_time_aead(Type, Key, IV, PlainText, AAD, TagLen, true)
- of
- RR ->
- ct:log("Works: ~p",[RR])
- catch
- CC:EE ->
- ct:log("~p:~p", [CC,EE])
- end,
- ct:fail("~p",[E])
- end,
- case crypto:crypto_one_time_aead(Type, Key, IV, CipherText, AAD, TruncatedCipherTag, false) of
- Plain ->
- ok;
- Other1 ->
- ct:fail({{crypto,
- block_decrypt,
- [{info,Info}, {key,Key}, {pt,PlainText}, {iv,IV}, {aad,AAD}, {ct,CipherText}, {tag,CipherTag},
- {truncated,TruncatedCipherTag}]},
- {expected, Plain},
- {got, Other1}})
+ error:Error -> {error, {Tag,T,Error}}
+ end.
+
+do_cipher_tests(F, TestVectors) when is_function(F,1) ->
+ {Passed,Failed} =
+ lists:partition(
+ fun(R) -> R == ok end,
+ lists:map(F, TestVectors)
+ ),
+ BothFailed = lists:filter(fun({ok,_}) -> false;
+ ({_,ok}) -> false;
+ (ok) -> false;
+ (_) -> true
+ end,
+ Failed),
+ ct:log("Passed: ~p, BothFailed: ~p OnlyOneFailed: ~p",
+ [length(Passed), length(BothFailed), length(Failed)-length(BothFailed)]),
+ case Failed of
+ [] ->
+ ct:comment("All ~p passed", [length(Passed)]);
+ _ ->
+ ct:log("~p",[hd(Failed)]),
+ ct:comment("Passed: ~p, BothFailed: ~p OnlyOneFailed: ~p",
+ [length(Passed), length(BothFailed), length(Failed)-length(BothFailed)]),
+ ct:fail("Failed", [])
end.
+
mk_bad_tag(CipherTag) ->
case <<0:(size(CipherTag))/unit:8>> of
CipherTag -> % The correct tag may happen to be a suite of zeroes
@@ -1491,30 +1495,6 @@ mk_bad_tag(CipherTag) ->
X
end.
-aead_cipher_bad_tag({Type, Key, PlainText, IV, AAD, CipherText, CipherTag, Info}) ->
- Plain = iolist_to_binary(PlainText),
- BadTag = mk_bad_tag(CipherTag),
- case crypto:crypto_one_time_aead(Type, Key, IV, CipherText, AAD, BadTag, false) of
- error ->
- ok;
- Plain ->
- ct:log("~p:~p~n info: ~p~n key: ~p~n pt: ~p~n iv: ~p~n aad: ~p~n ct: ~p~n tag: ~p~n bad tag: ~p~n",
- [?MODULE,?LINE,Info, Key, PlainText, IV, AAD, CipherText, CipherTag, BadTag]),
- ct:fail("Didn't fail on bad tag")
- end;
-aead_cipher_bad_tag({Type, Key, PlainText, IV, AAD, CipherText, CipherTag, TagLen, Info}) ->
- Plain = iolist_to_binary(PlainText),
- <<TruncatedCipherTag:TagLen/binary, _/binary>> = CipherTag,
- BadTruncatedTag = mk_bad_tag(TruncatedCipherTag),
- case crypto:crypto_one_time_aead(Type, Key, IV, CipherText, AAD, BadTruncatedTag, false) of
- error ->
- ok;
- Plain ->
- ct:log("~p:~p~n info: ~p~n key: ~p~n pt: ~p~n iv: ~p~n aad: ~p~n ct: ~p~n tag: ~p~n bad tag: ~p~n",
- [Info, Key, PlainText, IV, AAD, CipherText, TruncatedCipherTag, BadTruncatedTag]),
- ct:fail("Didn't fail on bad tag")
- end.
-
do_sign_verify({Type, undefined=Hash, Private, Public, Msg, Signature}) ->
case crypto:sign(eddsa, Hash, Msg, [Private,Type]) of
Signature ->
@@ -1598,45 +1578,65 @@ negative_verify(Type, Hash, Msg, Signature, Public, Options) ->
end.
do_public_encrypt({Type, Public, Private, Msg, Padding}) ->
+ ct:log("do_public_encrypt Type=~p, Padding=~p,~nPublic = ~p,~nPrivate = ~p,~nMsg = ~p.",
+ [Type, Padding, Public, Private, Msg]),
+ timer:sleep(100),
try
crypto:public_encrypt(Type, Msg, Public, Padding)
of
PublicEcn ->
+ ct:log("private_decrypt~nPublicEcn = ~p.", [PublicEcn]),
+ timer:sleep(100),
try
crypto:private_decrypt(Type, PublicEcn, Private, Padding)
of
Msg ->
+ ct:log("~p:~p ok", [?MODULE,?LINE]),
+ timer:sleep(100),
ok;
Other ->
+ ct:log("~p:~p Other = ~p", [?MODULE,?LINE,Other]),
+ timer:sleep(100),
ct:fail({{crypto, private_decrypt, [Type, PublicEcn, Private, Padding]}, {expected, Msg}, {got, Other}})
catch
CC:EE ->
+ ct:log("~p:~p EXC. ~p:~p", [?MODULE,?LINE,CC,EE]),
+ timer:sleep(100),
ct:fail({{crypto, private_decrypt, [Type, PublicEcn, Private, Padding]}, {expected, Msg}, {got, {CC,EE}}})
end
catch
CC:EE ->
+ ct:log("~p:~p EXC 2. ~p:~p", [?MODULE,?LINE,CC,EE]),
+ timer:sleep(100),
ct:fail({{crypto, public_encrypt, [Type, Msg, Public, Padding]}, {got, {CC,EE}}})
end.
do_private_encrypt({Type, Public, Private, Msg, Padding}) ->
+ ct:log("do_private_encrypt Type=~p, Padding=~p,~nPublic = ~p,~nPrivate = ~p,~nMsg = ~p.",
+ [Type, Padding, Public, Private, Msg]),
try
crypto:private_encrypt(Type, Msg, Private, Padding)
of
PrivEcn ->
try
+ ct:log("public_decrypt~nPrivEcn = ~p.", [PrivEcn]),
crypto:public_decrypt(Type, PrivEcn, Public, Padding)
of
Msg ->
+ ct:log("~p:~p ok", [?MODULE,?LINE]),
ok;
Other ->
+ ct:log("~p:~p Other = ~p", [?MODULE,?LINE,Other]),
ct:fail({{crypto, public_decrypt, [Type, PrivEcn, Public, Padding]}, {expected, Msg}, {got, Other}})
catch
CC:EE ->
+ ct:log("~p:~p EXC. ~p:~p", [?MODULE,?LINE,CC,EE]),
ct:fail({{crypto, public_decrypt, [Type, PrivEcn, Public, Padding]}, {expected, Msg}, {got, {CC,EE}}})
end
catch
CC:EE ->
+ ct:log("~p:~p EXC 2. ~p:~p", [?MODULE,?LINE,CC,EE]),
ct:fail({{crypto, private_encrypt, [Type, Msg, Private, Padding]}, {got, {CC,EE}}})
end.
@@ -1648,6 +1648,9 @@ do_generate_compute({srp = Type, UserPrivate, UserGenParams, UserComParams,
UserComParams),
SessionKey = crypto:compute_key(Type, UserPublic, {HostPublic, HostPrivate},
HostComParam);
+
+
+
do_generate_compute({dh, P, G}) ->
{UserPub, UserPriv} = crypto:generate_key(dh, [P, G]),
{HostPub, HostPriv} = crypto:generate_key(dh, [P, G]),
@@ -1655,6 +1658,7 @@ do_generate_compute({dh, P, G}) ->
SharedSecret = crypto:compute_key(dh, UserPub, HostPriv, [P, G]).
do_compute({ecdh = Type, Pub, Priv, Curve, SharedSecret}) ->
+ ct:log("~p ~p", [Type,Curve]),
Secret = crypto:compute_key(Type, Pub, Priv, Curve),
case Secret of
SharedSecret ->
@@ -1664,6 +1668,7 @@ do_compute({ecdh = Type, Pub, Priv, Curve, SharedSecret}) ->
end.
do_generate({Type, Curve, Priv, Pub}) when Type == ecdh ; Type == eddsa ->
+ ct:log("~p ~p", [Type,Curve]),
case crypto:generate_key(Type, Curve, Priv) of
{Pub, _} ->
ok;
@@ -1671,6 +1676,7 @@ do_generate({Type, Curve, Priv, Pub}) when Type == ecdh ; Type == eddsa ->
ct:fail({{crypto, generate_key, [Type, Priv, Curve]}, {expected, Pub}, {got, Other}})
end;
do_generate({rsa = Type, Mod, Exp}) ->
+ ct:log("~p", [Type]),
case crypto:info_fips() of
enabled when Mod < 3072 ->
ct:log("SKIP do_generate ~p FIPS=~p, Mod=~p Exp=~p", [Type, enabled, Mod, Exp]),
@@ -2101,6 +2107,8 @@ group_config(ecdh, Config) ->
Compute = ecdh(),
Generate = ecc(),
[{compute, Compute}, {generate, Generate} | Config];
+group_config(eddh, Config) ->
+ [{compute, []}, {generate, []} | Config];
group_config(dh, Config) ->
GenerateCompute = [dh()],
[{generate_compute, GenerateCompute} | Config];
@@ -2239,12 +2247,13 @@ gen_rsa_sign_verify_tests(Hashs, Msg, Public, Private, Opts) ->
gen_rsa_pub_priv_tests(Public, Private, Msg, OptsToTry) ->
- SupOpts = proplists:get_value(rsa_opts, crypto:supports(), []),
+ SupOpts = proplists:get_value(rsa_opts, crypto:supports(), []) --
+ [rsa_x931_padding],
lists:foldr(fun(Opt, Acc) ->
case rsa_opt_is_supported(Opt, SupOpts) of
true ->
[{rsa, Public, Private, Msg, Opt} | Acc];
- false ->
+ false ->
Acc
end
end, [], OptsToTry).
@@ -3975,11 +3984,13 @@ eddsa(ed448) ->
ecdh() ->
%% http://csrc.nist.gov/groups/STM/cavp/
- Curves = crypto:ec_curves() ++
- [X || X <- proplists:get_value(curves, crypto:supports(), []),
- lists:member(X, [x25519,x448])],
- TestCases =
- [{ecdh, hexstr2point("42ea6dd9969dd2a61fea1aac7f8e98edcc896c6e55857cc0", "dfbe5d7c61fac88b11811bde328e8a0d12bf01a9d204b523"),
+ Curves = crypto:supports(curves),
+ lists:filter(
+ fun ({_Type, _Pub, _Priv, Curve, _SharedSecret}) ->
+ lists:member(Curve, Curves)
+ end,
+
+ [{ecdh, hexstr2point("42ea6dd9969dd2a61fea1aac7f8e98edcc896c6e55857cc0", "dfbe5d7c61fac88b11811bde328e8a0d12bf01a9d204b523"),
hexstr2bin("f17d3fea367b74d340851ca4270dcb24c271f445bed9d527"),
secp192r1,
hexstr2bin("803d8ab2e5b6e6fca715737c3a82f7ce3c783124f6d51cd0")},
@@ -4085,11 +4096,8 @@ ecdh() ->
16#9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28dd9c9baf574a9419744897391006382a6f127ab1d9ac2d8c0a598726b,
x448,
hexstr2bin("07fff4181ac6cc95ec1c16a94a0f74d12da232ce40a77552281d282bb60c0b56fd2464c335543936521c24403085d59a449a5037514a879d")}
- ],
- lists:filter(fun ({_Type, _Pub, _Priv, Curve, _SharedSecret}) ->
- lists:member(Curve, Curves)
- end,
- TestCases).
+ ]
+ ).
dh() ->
{dh, 90970053988169282502023478715631717259407236400413906591937635666709823903223997309250405131675572047545403771567755831138144089197560332757755059848492919215391041119286178688014693040542889497092308638580104031455627238700168892909539193174537248629499995652186913900511641708112112482297874449292467498403, 2}.
@@ -4143,8 +4151,11 @@ ecc() ->
%% information about the curves see
%% http://csrc.nist.gov/encryption/dss/ecdsa/NISTReCur.pdf
%%
- Curves = crypto:ec_curves(),
- TestCases =
+ Curves = crypto:supports(curves),
+ lists:filter(
+ fun ({_Type, Curve, _Priv, _Pub}) ->
+ lists:member(Curve, Curves)
+ end,
[{ecdh,secp192r1,1,
hexstr2point("188DA80EB03090F67CBF20EB43A18800F4FF0AFD82FF1012",
"07192B95FFC8DA78631011ED6B24CDD573F977A11E794811")},
@@ -4174,12 +4185,9 @@ ecc() ->
hexstr2bin("8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a")},
{ecdh, x25519,
hexstr2bin("5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb"),
- hexstr2bin("de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f")}],
- lists:filter(fun ({_Type, Curve, _Priv, _Pub}) ->
- lists:member(Curve, Curves)
- end,
- TestCases).
-
+ hexstr2bin("de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f")}
+ ]
+ ).
int_to_bin(X) when X < 0 -> int_to_bin_neg(X, []);
int_to_bin(X) -> int_to_bin_pos(X, []).
diff --git a/lib/crypto/test/crypto_property_test_SUITE.erl b/lib/crypto/test/crypto_property_test_SUITE.erl
index bf137363e8..9c958007c7 100644
--- a/lib/crypto/test/crypto_property_test_SUITE.erl
+++ b/lib/crypto/test/crypto_property_test_SUITE.erl
@@ -35,6 +35,7 @@ init_per_suite(Config) ->
try crypto:start() of
ok -> true;
{error, already_started} -> true;
+ {error,{already_started,crypto}} -> true;
_ -> false
catch
_:_ -> false
diff --git a/lib/crypto/vsn.mk b/lib/crypto/vsn.mk
index 72f3b9b792..477280d84b 100644
--- a/lib/crypto/vsn.mk
+++ b/lib/crypto/vsn.mk
@@ -1 +1 @@
-CRYPTO_VSN = 4.6.5
+CRYPTO_VSN = 4.7
diff --git a/lib/debugger/doc/src/notes.xml b/lib/debugger/doc/src/notes.xml
index 64af47a4fb..5e3f2ce878 100644
--- a/lib/debugger/doc/src/notes.xml
+++ b/lib/debugger/doc/src/notes.xml
@@ -33,6 +33,43 @@
<p>This document describes the changes made to the Debugger
application.</p>
+<section><title>Debugger 5.0</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>EEP-52 has been implemented.</p>
+ <p>In binary matching, the size of the segment to be
+ matched is now allowed to be a guard expression, and
+ similarly in map matching the keys can now be guard
+ expressions. See the Erlang Reference Manual and
+ Programming Examples for more details.</p>
+ <p>Language compilers or code generators that generate
+ Core Erlang code may need to be updated to be compatible
+ with the compiler in OTP 23. For more details, see the
+ section Backwards Compatibility in <url
+ href="http://erlang.org/eeps/eep-0052.html">EEP
+ 52</url>.</p>
+ <p>
+ Own Id: OTP-14708</p>
+ </item>
+ <item>
+ <p>The deprecated <c>erlang:get_stacktrace/0</c> BIF now
+ returns an empty list instead of a stacktrace. To
+ retrieve the stacktrace, use the extended try/catch
+ syntax that was introduced in OTP 21.
+ <c>erlang:get_stacktrace/0</c> is scheduled for removal
+ in OTP 24.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16484</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Debugger 4.2.8</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/debugger/vsn.mk b/lib/debugger/vsn.mk
index 06fc743270..8e334a00f5 100644
--- a/lib/debugger/vsn.mk
+++ b/lib/debugger/vsn.mk
@@ -1 +1 @@
-DEBUGGER_VSN = 4.2.8
+DEBUGGER_VSN = 5.0
diff --git a/lib/dialyzer/doc/src/notes.xml b/lib/dialyzer/doc/src/notes.xml
index b5e6ffb485..675a2b43ef 100644
--- a/lib/dialyzer/doc/src/notes.xml
+++ b/lib/dialyzer/doc/src/notes.xml
@@ -32,6 +32,20 @@
<p>This document describes the changes made to the Dialyzer
application.</p>
+<section><title>Dialyzer 4.2</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p> Improve handling of <c>maps:remove/2</c>. </p>
+ <p>
+ Own Id: OTP-16055 Aux Id: ERL-1002 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Dialyzer 4.1.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/dialyzer/vsn.mk b/lib/dialyzer/vsn.mk
index ee680f3bcf..b5a3bbf2b4 100644
--- a/lib/dialyzer/vsn.mk
+++ b/lib/dialyzer/vsn.mk
@@ -1 +1 @@
-DIALYZER_VSN = 4.1.1
+DIALYZER_VSN = 4.2
diff --git a/lib/edoc/doc/src/notes.xml b/lib/edoc/doc/src/notes.xml
index 48bc5d9c74..2871a09476 100644
--- a/lib/edoc/doc/src/notes.xml
+++ b/lib/edoc/doc/src/notes.xml
@@ -32,6 +32,32 @@
<p>This document describes the changes made to the EDoc
application.</p>
+<section><title>Edoc 0.12</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p> Remove Inets dependency from EDoc. </p>
+ <p>
+ Own Id: OTP-15999 Aux Id: PR-2317 </p>
+ </item>
+ <item>
+ <p>
+ Add support for overloaded Erlang specifications.</p>
+ <p>
+ Own Id: OTP-16407 Aux Id: PR-2430 </p>
+ </item>
+ <item>
+ <p>Refactored the internal handling of deprecated and
+ removed functions.</p>
+ <p>
+ Own Id: OTP-16469</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Edoc 0.11</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/edoc/src/edoc_data.erl b/lib/edoc/src/edoc_data.erl
index ed81c9f4c3..3075e47942 100644
--- a/lib/edoc/src/edoc_data.erl
+++ b/lib/edoc/src/edoc_data.erl
@@ -230,9 +230,11 @@ callback({N, A}, _Env, _Opts) ->
%% <!ELEMENT throws (type, localdef*)>
%% <!ELEMENT equiv (expr, see?)>
%% <!ELEMENT expr (#PCDATA)>
-
-function({N, A}, As, Export, Ts, Env, Opts) ->
- {Args, Ret, Spec} = signature(Ts, As, Env),
+function({N, A}, []=As, Export, Ts, Env, Opts)->
+ function({N, A}, [As], Export, Ts, Env, Opts);
+function({N, A}, [HAs | _]=As, Export, Ts, Env, Opts) when not is_list(HAs) ->
+ function({N, A}, [As], Export, Ts, Env, Opts);
+function({N, A}, As0, Export, Ts, Env, Opts) ->
{function, [{name, atom_to_list(N)},
{arity, integer_to_list(A)},
{exported, case Export of
@@ -240,13 +242,8 @@ function({N, A}, As, Export, Ts, Env, Opts) ->
false -> "no"
end},
{label, edoc_refs:to_label(edoc_refs:function(N, A))}],
- [{args, [{arg, [{argName, [atom_to_list(A)]}] ++ description(D)}
- || {A, D} <- Args]}]
- ++ Spec
- ++ case Ret of
- [] -> [];
- _ -> [{returns, description(Ret)}]
- end
+ lists:append([get_args(lists:nth(Clause, As0), Ts, Clause, Env)
+ || Clause <- lists:seq(1, length(As0))])
++ get_throws(Ts, Env)
++ get_equiv(Ts, Env)
++ get_doc(Ts)
@@ -256,6 +253,16 @@ function({N, A}, As, Export, Ts, Env, Opts) ->
++ todos(Ts, Opts)
}.
+get_args(As, Ts, Clause, Env) ->
+ {Args, Ret, Spec} = signature(Ts, As, Clause, Env),
+ [{args, [{arg, [{argName, [atom_to_list(A)]}] ++ description(D)}
+ || {A, D} <- Args]}]
+ ++ Spec
+ ++ case Ret of
+ [] -> [];
+ _ -> [{returns, description(Ret)}]
+ end.
+
get_throws(Ts, Env) ->
case get_tags(throws, Ts) of
[Throws] ->
@@ -406,10 +413,10 @@ todos(Tags, Opts) ->
[]
end.
-signature(Ts, As, Env) ->
+signature(Ts, As, Clause, Env) ->
case get_tags(spec, Ts) of
[T] ->
- Spec = T#tag.data,
+ Spec = maybe_nth(Clause, T#tag.data),
R = merge_returns(Spec, Ts),
As0 = edoc_types:arg_names(Spec),
Ds0 = edoc_types:arg_descs(Spec),
@@ -424,6 +431,11 @@ signature(Ts, As, Env) ->
{[{A, ""} || A <- fix_argnames(As, S, 1)], [], []}
end.
+maybe_nth(N, List) when is_list(List) ->
+ lists:nth(N, List);
+maybe_nth(1, Other) ->
+ Other.
+
params(Ts) ->
[T#tag.data || T <- get_tags(param, Ts)].
diff --git a/lib/edoc/src/edoc_extract.erl b/lib/edoc/src/edoc_extract.erl
index 390851e9ef..f7e2c28b6f 100644
--- a/lib/edoc/src/edoc_extract.erl
+++ b/lib/edoc/src/edoc_extract.erl
@@ -634,7 +634,7 @@ select_spec(Ts, _Where, _Specs) ->
selected_specs([], Ts) ->
Ts;
selected_specs([F], [_ | Ts]) ->
- [edoc_specs:spec(F, _Clause=1) | Ts].
+ [edoc_specs:spec(F) | Ts].
%% Macros for modules
diff --git a/lib/edoc/src/edoc_layout.erl b/lib/edoc/src/edoc_layout.erl
index 47ff7b21fc..3643419ba1 100644
--- a/lib/edoc/src/edoc_layout.erl
+++ b/lib/edoc/src/edoc_layout.erl
@@ -357,10 +357,10 @@ label_href(Content, F) ->
functions(Fs, Opts) ->
Es = lists:flatmap(fun ({Name, E}) -> function(Name, E, Opts) end, Fs),
if Es == [] -> [];
- true ->
- [?NL,
- {h2, [{a, [{name, ?FUNCTIONS_LABEL}], [?FUNCTIONS_TITLE]}]},
- ?NL | Es]
+ true ->
+ [?NL,
+ {h2, [{a, [{name, ?FUNCTIONS_LABEL}], [?FUNCTIONS_TITLE]}]},
+ ?NL | Es]
end.
function(Name, E=#xmlElement{content = Es}, Opts) ->
@@ -369,22 +369,24 @@ function(Name, E=#xmlElement{content = Es}, Opts) ->
label_anchor(function_header(Name, E, " *"), E)},
?NL]
++ [{'div', [{class, "spec"}],
- [?NL,
- {p,
- case typespec(get_content(typespec, Es), Opts) of
+ case [typespec(T, Opts) || T <- get_contents(typespec, Es)] of
[] ->
- signature(get_content(args, Es),
- atom(get_attrval(name, E), Opts));
- Spec -> Spec
- end},
- ?NL]
- ++ case params(get_content(args, Es)) of
+ [?NL,{p,
+ signature(get_content(args, Es),
+ atom(get_attrval(name, E), Opts))
+ },?NL];
+ Specs ->
+ [?NL]++[{p, Spec} || Spec <- Specs]++[?NL]
+ end
+ ++ case [params(A) || A <- get_contents(args, Es)] of
[] -> [];
- Ps -> [{p, Ps}, ?NL]
+ As ->
+ lists:append([[{p, Ps}, ?NL] || Ps <- As])
end
- ++ case returns(get_content(returns, Es)) of
+ ++ case [returns(Ret) || Ret <- get_contents(returns, Es)] of
[] -> [];
- Rs -> [{p, Rs}, ?NL]
+ Rets ->
+ lists:append([[{p, Rs}, ?NL] || Rs <- Rets])
end}]
++ throws(Es, Opts)
++ equiv_p(Es)
@@ -968,12 +970,8 @@ seq(F, [E | Es], Sep, Tail) ->
seq(_F, [], _Sep, Tail) ->
Tail.
-get_elem(Name, [#xmlElement{name = Name} = E | Es]) ->
- [E | get_elem(Name, Es)];
-get_elem(Name, [_ | Es]) ->
- get_elem(Name, Es);
-get_elem(_, []) ->
- [].
+get_elem(Name, Es) ->
+ [E || #xmlElement{name=N}=E <- Es, N=:=Name].
get_attr(Name, [#xmlAttribute{name = Name} = A | As]) ->
[A | get_attr(Name, As)];
@@ -989,6 +987,13 @@ get_attrval(Name, #xmlElement{attributes = As}) ->
[] -> ""
end.
+get_contents(Name, Es) ->
+ case get_elem(Name, Es) of
+ [] -> [];
+ Elems ->
+ [Es1 || #xmlElement{content = Es1} <- Elems]
+ end.
+
get_content(Name, Es) ->
case get_elem(Name, Es) of
[#xmlElement{content = Es1}] ->
diff --git a/lib/edoc/src/edoc_specs.erl b/lib/edoc/src/edoc_specs.erl
index 7b451c43f8..19f890ed8b 100644
--- a/lib/edoc/src/edoc_specs.erl
+++ b/lib/edoc/src/edoc_specs.erl
@@ -21,7 +21,7 @@
-module(edoc_specs).
--export([type/2, spec/2, dummy_spec/1, docs/2]).
+-export([type/2, spec/1, dummy_spec/1, docs/2]).
-export([add_data/4, tag/1, is_tag/1]).
@@ -67,15 +67,14 @@ type(Form, TypeDocs) ->
type = d2e(opaque2abstr(Name, Type))},
Doc}}.
--spec spec(Form::syntaxTree(), ClauseN::pos_integer()) -> #tag{}.
+-spec spec(Form::syntaxTree()) -> #tag{}.
%% @doc Convert an Erlang spec to EDoc representation.
-spec(Form, Clause) ->
+spec(Form) ->
{Name, _Arity, TypeSpecs} = get_spec(Form),
- TypeSpec = lists:nth(Clause, TypeSpecs),
- #tag{name = spec, line = get_line(element(2, TypeSpec)),
+ #tag{name = spec, line = get_line(element(2, lists:nth(1, TypeSpecs))),
origin = code,
- data = aspec(d2e(TypeSpec), Name)}.
+ data = [aspec(d2e(TypeSpec), Name) || TypeSpec <- TypeSpecs]}.
-spec dummy_spec(Form::syntaxTree()) -> #tag{}.
@@ -264,8 +263,9 @@ use_tags([#tag{origin = code}=T | Ts], E, TypeTable, NTs) ->
use_tags([T | Ts], E, TypeTable, NTs) ->
use_tags(Ts, E, TypeTable, [T | NTs]).
-params(#tag{name = spec, data=#t_spec{type = #t_fun{args = As}}}, Default) ->
- parms(As, Default).
+
+params(#tag{name = spec, data=Data}, Default) when is_list(Data) ->
+ [parms(As, Default) || #t_spec{type = #t_fun{args = As}} <- Data].
parms([], []) ->
[];
@@ -485,13 +485,17 @@ entries([E0 | Es], P, Opts) ->
entries([], _P, _Opts) ->
[].
-specs([#tag{line = L, name = spec, origin = code, data = Spec}=Tag0 | Tags],
+specs([#tag{line = L, name = spec, origin = code, data = Specs}=Tag0 | Tags],
P0) ->
- #t_spec{type = Type0, defs = Defs0} = Spec,
P = P0#parms{line = L},
- Type = xrecs(Type0, P),
- Defs = xrecs(Defs0, P),
- Tag = Tag0#tag{data = Spec#t_spec{type = Type, defs = Defs}},
+ Data =
+ [ begin
+ #t_spec{type = Type0, defs = Defs0} = Spec,
+ Type = xrecs(Type0, P),
+ Defs = xrecs(Defs0, P),
+ Spec#t_spec{type = Type, defs = Defs}
+ end || Spec <- Specs],
+ Tag = Tag0#tag{data = Data},
[Tag | specs(Tags, P)];
specs([Tag | Tags], P) ->
[Tag | specs(Tags, P)];
diff --git a/lib/edoc/vsn.mk b/lib/edoc/vsn.mk
index 3510fdfccf..5d2bbe769d 100644
--- a/lib/edoc/vsn.mk
+++ b/lib/edoc/vsn.mk
@@ -1 +1 @@
-EDOC_VSN = 0.11
+EDOC_VSN = 0.12
diff --git a/lib/erl_docgen/Makefile b/lib/erl_docgen/Makefile
index 7e9cc824ec..acb1ea9776 100644
--- a/lib/erl_docgen/Makefile
+++ b/lib/erl_docgen/Makefile
@@ -36,6 +36,6 @@ SPECIAL_TARGETS =
#
include $(ERL_TOP)/make/otp_subdir.mk
-DIA_PLT_APPS=edoc xmerl
+DIA_PLT_APPS=edoc xmerl syntax_tools crypto
include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/erl_docgen/doc/src/doc_storage.xml b/lib/erl_docgen/doc/src/doc_storage.xml
index 2fd804b522..2c719b3f20 100644
--- a/lib/erl_docgen/doc/src/doc_storage.xml
+++ b/lib/erl_docgen/doc/src/doc_storage.xml
@@ -21,7 +21,7 @@
limitations under the License.
</legalnotice>
- <title>Documentation Storage</title>
+ <title>EEP-48: Implementation in Erlang/OTP</title>
<prepared></prepared>
<docno></docno>
<date></date>
@@ -31,29 +31,103 @@
<section>
<title>EEP-48: Documentation storage and format</title>
- <p><url href="https://www.erlang.org/erlang-enhancement-proposals/eep-0048.html">EEP-48</url>
+ <p><seeguide marker="kernel:eep48_chapter">EEP-48</seeguide>
defines a common documentation storage format for module documentation in the Erlang/OTP
ecosystem. Erl_Docgen can generate documentation in this format from XML files following
the DTD's descibed in the other User's Guides in this application.</p>
- <p></p>
<p>Some special considerations have to be taken when writing documentation that
should also be available through EEP-48 style storage.</p>
<list>
- <item>The <c>#PCDATA</c> within <c>&lt;name&gt;</c> tags must be parseable to figure out the arity of the function.</item>
+ <item>The <c>#PCDATA</c> within <c>&lt;name&gt;</c> tags must be parseable
+ to figure out the arity of the function.</item>
<item>It is not allowed to mix <c>&lt;name&gt;</c> tags with #PCDATA and attributes.</item>
- <item>All <c>&lt;name&gt;</c> tags within <c>&lt;func&gt;</c> has to have a <c>since</c> attribute.</item>
+ <item>All <c>&lt;name&gt;</c> tags within <c>&lt;func&gt;</c>
+ has to have a <c>since</c> attribute.</item>
<item>All callback function documentations have to start with a <c>Module</c> prefix.</item>
</list>
</section>
<section>
<title>Erlang Documentation Format</title>
- <p>When generating documentation for generic storage</p>
+ <p>When generating documentation for EEP-48 Erl_Docgen uses the format mime type
+ &lt;&lt;"application/erlang+html"&gt;&gt;. The documentation content is an Erlang
+ term that represents an HTML like structure.</p>
+ <code>
+-type chunk_elements() :: [chunk_element()].
+-type chunk_element() :: {chunk_element_type(),chunk_element_attrs(),
+ chunk_elements()} | unicode:unicode_binary().
+-type chunk_element_attrs() :: [chunk_element_attr()].
+-type chunk_element_attr() :: {atom(),unicode:unicode_binary()}.
+-type chunk_element_type() :: chunk_element_inline_type() | chunk_element_block_type().
+-type chunk_element_inline_type() :: a | code | em | i.
+-type chunk_element_block_type() :: p | 'div' | br | pre | ul |
+ ol | li | dl | dt | dd | h1 | h2 | h3.
+ </code>
+ <p>The different element types follow their HTML meaning when rendered.
+ The following are some general rules for how the chunk elements are allowed
+ to be generated.</p>
+ <list>
+ <item>Inline and <c>pre</c> elements are not allowed to contain block elements.</item>
+ <item><c>p</c> elements are not allowed to be nested.</item>
+ </list>
+ <p>The attributes on some elements have a special meaning.</p>
+ <taglist>
+ <tag><c>{'div',[{class,unicode:unicode_binary()}],_}</c></tag>
+ <item>The class name will be used to provide styling to the content in the div.
+ The types of classes used by Erlang/OTP are: <c>warning</c>, <c>note</c>, <c>do</c>,
+ <c>dont</c> and <c>quote</c>.</item>
+ <tag><c><![CDATA[{ul,[{class,<<"types">>}],_}]]></c></tag>
+ <item>This is a list containing type documentation.</item>
+ <tag><c><![CDATA[{li,[{name,TypeName :: unicode:unicode_binary()}],_}]]></c></tag>
+ <item>A list item with a type specification located in the metadata of this modules
+ EEP-48 documentation. The implementation should look for the AST representation of
+ the type under the <c>types</c> key. This attribute is only valid under a <c>ul</c>
+ with class &lt;&lt;"types"&gt;&gt;.</item>
+ <tag><c><![CDATA[{li,[{class,<<"type">>}],_}]]></c></tag>
+ <item>A list item with the type described in the Erlang Documentation Format.
+ This attribute is only valid under a <c>ul</c> with class &lt;&lt;"types"&gt;&gt;.</item>
+ <tag><c><![CDATA[{li,[{class,<<"description">>}],_}]]></c></tag>
+ <item>A list item with the description of the type previous in the list.
+ This attribute is only valid under a <c>ul</c> with class &lt;&lt;"types"&gt;&gt;.</item>
+ </taglist>
+ <p>The <seemfa marker="stdlib:shell_docs#validate/1"><c>shell_docs:validate/1</c></seemfa>
+ function can be used to do a validation of the Erlang Documentation Format.</p>
+ </section>
+
+ <section>
+ <title>Erlang Documentation extra Metadata</title>
+ <p>Erlang/OTP uses some extra metadata fields to embed more information into the EEP-48 docs.</p>
+ <list>
+ <item>Fields on module level:
+ <taglist>
+ <tag><c>otp_doc_vsn := {non_neg_integer(),non_neg_integer(),non_neg_integer()}</c></tag>
+ <item>Describes the version of the Erlang Documentation Format used
+ within this module</item>
+ <tag><c>types := #{ TypeName :: unicode:unicode_binary() => TypeAST }</c></tag>
+ <item>A map containing the AST of the types that are part of this module.
+ This map is used to by functions and callbacks to render the types inline
+ into their documentation.</item>
+ </taglist>
+ </item>
+ <item>Fields on functions and types:
+ <taglist>
+ <tag><c>signature := SpecAST</c></tag>
+ <item>The spec AST associated with this function. It is used to render a more
+ descriptive slogan for the documentation entry.</item>
+ <tag><c>equiv := {Type,Name,Arity}</c></tag>
+ <item>The current function/type shares documentation with another function/type.
+ This means that if this and the target function/type are to be shown at the
+ same time only the prototype of this function/type should will be displayed
+ and the documentation will use a common body of text.</item>
+ </taglist>
+ </item>
+ </list>
</section>
<section>
<title>See Also</title>
<p>
+ <seeguide marker="kernel:eep48_chapter"></seeguide>
<seeerl marker="stdlib:shell_docs"><c>shell_docs(3)</c></seeerl>,
<seemfa marker="kernel:code#get_doc/1"><c>code:get_doc(3)</c></seemfa>
</p>
diff --git a/lib/erl_docgen/doc/src/notes.xml b/lib/erl_docgen/doc/src/notes.xml
index e5076a4790..08bcc3d8da 100644
--- a/lib/erl_docgen/doc/src/notes.xml
+++ b/lib/erl_docgen/doc/src/notes.xml
@@ -31,7 +31,53 @@
</header>
<p>This document describes the changes made to the <em>erl_docgen</em> application.</p>
- <section><title>Erl_Docgen 0.11</title>
+ <section><title>Erl_Docgen 1.0</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Embedded documentation (also known as Documentation
+ Chunks) is now also available in the form of files
+ according to <url
+ href="https://www.erlang.org/erlang-enhancement-proposals/eep-0048.html">EEP-48</url>.
+ The Documentation Chunks are produced by default when
+ building the other Erlang/OTP documentation. If you want
+ to only build the embedded documentation you can pass the
+ <c>DOC_TARGETS=chunks</c> environment variable to make.</p>
+ <p>
+ Own Id: OTP-16406</p>
+ </item>
+ <item>
+ <p>
+ Minor DTD additions.</p>
+ <p>
+ Own Id: OTP-16497</p>
+ </item>
+ <item>
+ <p>
+ The <c>seealso</c> tag has been replaced with type aware
+ tags instead. The new tags are:
+ <c>seemfa|seeerl|seetype|seeapp|seecom|seecref|seefile|seeguide</c>.</p>
+ <p>
+ <c>fsdescription</c> has been added for adding a title to
+ groups of functions, for instance Module Callbacks.</p>
+ <p>
+ The <c>dtd</c>s of all documentation files have been
+ trimmed from all unused or rarely-used tags.</p>
+ <p>
+ Unused <c>dtd</c>s have been removed.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16503</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erl_Docgen 0.11</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/erl_docgen/priv/bin/specs_gen.escript b/lib/erl_docgen/priv/bin/specs_gen.escript
index 96b63aa667..5b75a83a7e 100644
--- a/lib/erl_docgen/priv/bin/specs_gen.escript
+++ b/lib/erl_docgen/priv/bin/specs_gen.escript
@@ -89,8 +89,9 @@ call_edoc(FileSpec, InclFs, Dir) ->
ok = write_text(Text, File, Dir),
rename(Dir, File)
catch
- _:_ ->
+ E:R:ST ->
io:format("EDoc could not process file '~s'\n", [File]),
+ io:format("~p:~p ~p\n", [E,R,ST]),
clean_up(Dir),
halt(3)
end.
diff --git a/lib/erl_docgen/priv/bin/validate_links.escript b/lib/erl_docgen/priv/bin/validate_links.escript
index 41d533fb58..4d93d9c253 100755
--- a/lib/erl_docgen/priv/bin/validate_links.escript
+++ b/lib/erl_docgen/priv/bin/validate_links.escript
@@ -259,11 +259,6 @@ validate_link(Filename, LinkType = "seetype", Line, Link, CachedFiles) ->
_ ->
validate_type(Line,LinkType,read_link(Line, ParsedLink, CachedFiles))
end;
-validate_link(Filename, "seeerl" = LinkType, Line, Link, CachedFiles) ->
- ParsedLink = parse_link(Filename, maps:get(m2a,CachedFiles), Link),
- TargetInfo = read_link(Line, ParsedLink, CachedFiles),
- validate_type(Line,LinkType,TargetInfo),
- validate_marker(Line,ParsedLink,TargetInfo);
validate_link({"jinterface","jinterface_users_guide"},"seefile",_, _, _) ->
%% Skip links to java documentation
ok;
diff --git a/lib/erl_docgen/priv/dtd/common.dtd b/lib/erl_docgen/priv/dtd/common.dtd
index d4d5d989a5..cc186fe5d6 100644
--- a/lib/erl_docgen/priv/dtd/common.dtd
+++ b/lib/erl_docgen/priv/dtd/common.dtd
@@ -51,7 +51,7 @@
<!ELEMENT list (item+) >
<!ATTLIST list type (ordered|bulleted) "bulleted" >
-<!ELEMENT taglist (marker*,tag,item+)+ >
+<!ELEMENT taglist (tag,item+)+ >
<!ELEMENT tag (#PCDATA|c|i|em|br|%refs;|marker|anno)* >
<!ELEMENT item (%inline;|%block;|warning|note|dont|do|quote|table)* >
diff --git a/lib/erl_docgen/priv/xsl/db_html.xsl b/lib/erl_docgen/priv/xsl/db_html.xsl
index 15c86adf2d..c14e7c9a71 100644
--- a/lib/erl_docgen/priv/xsl/db_html.xsl
+++ b/lib/erl_docgen/priv/xsl/db_html.xsl
@@ -2267,10 +2267,10 @@
</xsl:variable>
<xsl:choose>
<xsl:when test="string-length($fname2) > 0">
- <xsl:value-of select="$fname2"/>
+ <xsl:value-of select="normalize-space($fname2)"/>
</xsl:when>
<xsl:otherwise>
- <xsl:value-of select="$fname1"/>
+ <xsl:value-of select="normalize-space($fname1)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
diff --git a/lib/erl_docgen/src/docgen_edoc_xml_cb.erl b/lib/erl_docgen/src/docgen_edoc_xml_cb.erl
index 60d73c279d..47ca1690e2 100644
--- a/lib/erl_docgen/src/docgen_edoc_xml_cb.erl
+++ b/lib/erl_docgen/src/docgen_edoc_xml_cb.erl
@@ -757,23 +757,21 @@ functions(Fs) ->
function(_Name, E=#xmlElement{content = Es}) ->
TypeSpec = get_content(typespec, Es),
- [?NL,{func, [ ?NL,
- {name, [{since,""}],
- case funcheader(TypeSpec) of
- [] ->
- signature(get_content(args, Es),
- get_attrval(name, E));
- Spec -> Spec
- end
- },
- ?NL,{fsummary, fsummary(Es)},
- ?NL,local_types(TypeSpec),
- ?NL,{desc,
- label_anchor(E)++
- deprecated(Es)++
- fulldesc(Es)++
- seealso_function(Es)}
- ]}].
+ FuncHeaders =
+ case funcheader(TypeSpec) of
+ [] ->
+ [signature(get_content(args, Es), get_attrval(name, E))];
+ Specs ->
+ Specs
+ end,
+ [?NL, {func, [?NL]++
+ [{name, [{since,""}], Spec} || Spec <- FuncHeaders]++
+ [?NL, {fsummary, fsummary(Es)},
+ ?NL, local_types(TypeSpec),
+ ?NL, {desc, label_anchor(E)++
+ deprecated(Es)++
+ fulldesc(Es)++
+ seealso_function(Es)}]}].
fsummary([]) -> ["\s"];
fsummary(Es) ->
@@ -817,7 +815,9 @@ arg(#xmlElement{content = Es}) ->
funcheader([]) -> [];
funcheader(Es) ->
- [t_name(get_elem(erlangName, Es))] ++ t_utype(get_elem(type, Es)).
+ Name = t_name(get_elem(erlangName, Es)),
+ [ [Name] ++ t_utype([E]) || E <- get_elem(type, Es)].
+
local_types([]) -> [];
local_types(Es) ->
@@ -1035,7 +1035,7 @@ author(E=#xmlElement{}) ->
end,
[?NL,{aname,[Name]},?NL,{email,[Mail]}].
-t_name([E]) ->
+t_name([E | _]) ->
N = get_attrval(name, E),
case get_attrval(module, E) of
"" -> N;
@@ -1246,12 +1246,15 @@ get_attrval(Name, #xmlElement{attributes = As}) ->
%% get_content(Tag, Es1) -> Es2
%% If there is one element in Es1 with name Tag, returns its contents,
-%% otherwise []
+%% if there are no tags, return [],
+%% if there are multiple, merge their contents.
get_content(Name, Es) ->
case get_elem(Name, Es) of
- [#xmlElement{content = Es1}] ->
- Es1;
- [] -> []
+ [#xmlElement{content = Es1}] ->
+ Es1;
+ [] -> [];
+ Elems ->
+ lists:append([Es1 || #xmlElement{content = Es1} <- Elems])
end.
%% get_text(Tag, Es) -> string()
diff --git a/lib/erl_docgen/src/docgen_xml_to_chunk.erl b/lib/erl_docgen/src/docgen_xml_to_chunk.erl
index c57ecac213..6c9ae59996 100644
--- a/lib/erl_docgen/src/docgen_xml_to_chunk.erl
+++ b/lib/erl_docgen/src/docgen_xml_to_chunk.erl
@@ -525,30 +525,60 @@ func2func({func,Attr,Contents}) ->
_ = VerifyNameList(NameList,fun([]) -> ok end),
FAs = [TagsToFA(FAttr) || {name,FAttr,[]} <- NameList ],
+ SortedFAs = lists:usort(FAs),
FAClauses = lists:usort([{TagsToFA(FAttr),proplists:get_value(clause_i,FAttr)}
|| {name,FAttr,[]} <- NameList ]),
- Signature = [iolist_to_binary([F,"/",A]) || {F,A} <- FAs],
- lists:map(
- fun({F,A}) ->
- Specs = [{func_to_atom(CF),list_to_integer(CA),C}
- || {{CF,CA},C} <- FAClauses,
- F =:= CF, A =:= CA],
- {function,[{name,F},{arity,list_to_integer(A)},
- {signature,Signature},
- {meta,SinceMD#{ signature => Specs }}],
- ContentsNoName}
- end, lists:usort(FAs));
+
+ MakeFunc = fun({F,A}, MD, Doc) ->
+ Specs = [begin
+ {function,Name} = func_to_atom(CF),
+ {Name,list_to_integer(CA),C}
+ end || {{CF,CA},C} <- FAClauses,
+ F =:= CF, A =:= CA],
+ {function,[{name,F},{arity,list_to_integer(A)},
+ {signature,[iolist_to_binary([F,"/",A])]},
+ {meta,MD#{ signature => Specs }}],
+ Doc}
+ end,
+
+ Base = MakeFunc(hd(SortedFAs), SinceMD, ContentsNoName),
+
+ {BaseF,BaseA} = hd(SortedFAs),
+ MD = SinceMD#{ equiv => {function,list_to_atom(BaseF),list_to_integer(BaseA)}},
+ Equiv = lists:map(
+ fun(FA) ->
+ MakeFunc(FA, MD, [])
+ end, tl(SortedFAs)),
+ [Base | Equiv];
NameList ->
%% Manual style function docs
- FAs = lists:flatten([func_to_tuple(NameString) || {name, _Attr, NameString} <- NameList]),
+ FAs = lists:foldl(
+ fun({name,_,NameString}, Acc) ->
+ FAs = func_to_tuple(NameString),
+ lists:foldl(
+ fun(FA, FAAcc) ->
+ Slogan = maps:get(FA, FAAcc, []),
+ FAAcc#{ FA => [strip_tags(NameString)|Slogan] }
+ end, Acc, FAs)
+ end, #{}, NameList),
_ = VerifyNameList(NameList,fun([_|_]) -> ok end),
- Signature = [strip_tags(NameString) || {name, _Attr, NameString} <- NameList],
- [{function,[{name,F},{arity,A},
- {signature,Signature},
- {meta,SinceMD}],ContentsNoName}
- || {F,A} <- lists:usort(FAs)]
+ SortedFAs = lists:usort(maps:to_list(FAs)),
+
+ {{BaseF, BaseA}, BaseSig} = hd(SortedFAs),
+
+ Base = {function,[{name,BaseF},{arity,BaseA},
+ {signature,BaseSig},
+ {meta,SinceMD}],
+ ContentsNoName},
+
+ Equiv = [{function,
+ [{name,F},{arity,A},
+ {signature,Signature},
+ {meta,SinceMD#{ equiv => {function,list_to_atom(BaseF),BaseA}}}],[]}
+ || {{F,A},Signature} <- tl(SortedFAs)],
+ [Base | Equiv]
end,
transform(Functions,[]).
@@ -644,7 +674,7 @@ to_chunk(Dom, Source, Module, AST) ->
TypeEntries =
lists:map(
fun({datatype,Attr,Descr}) ->
- TypeName = func_to_atom(proplists:get_value(name,Attr)),
+ {function, TypeName} = func_to_atom(proplists:get_value(name,Attr)),
TypeArity = case proplists:get_value(n_vars,Attr) of
undefined ->
find_type_arity(TypeName, TypeMap);
@@ -662,24 +692,48 @@ to_chunk(Dom, Source, Module, AST) ->
Sig ->
#{ signature => [Sig] }
end,
- docs_v1_entry(type, Anno, TypeName, TypeArity, TypeSignature, MetaSig, Descr)
+
+ MetaDepr
+ = case otp_internal:obsolete_type(Module, TypeName, TypeArity) of
+ {deprecated, Text} ->
+ MetaSig#{ deprecated =>
+ unicode:characters_to_binary(
+ erl_lint:format_error({deprecated_type,{Module,TypeName,TypeArity}, Text})) };
+ %% Commented out to make dialyzer happy
+ %% {deprecated, Replacement, Rel} ->
+ %% MetaSig#{ deprecated =>
+ %% unicode:characters_to_binary(
+ %% erl_lint:format_error({deprecated_type,{Module,TypeName,TypeArity}, Replacement, Rel})) };
+ no ->
+ MetaSig
+ end,
+
+ docs_v1_entry(type, Anno, TypeName, TypeArity, TypeSignature, MetaDepr, Descr)
end, Types),
Functions = lists:flatten([Functions || {functions,[],Functions} <- Mcontent]),
FuncEntrys =
- lists:flatmap(
+ lists:map(
fun({function,Attr,Fdoc}) ->
- case func_to_atom(proplists:get_value(name,Attr)) of
- callback ->
- [];
- Name ->
- Arity = proplists:get_value(arity,Attr),
- Signature = proplists:get_value(signature,Attr),
- FMeta = proplists:get_value(meta,Attr),
- MetaWSpec = add_spec(AST,FMeta),
- [docs_v1_entry(function, Anno, Name, Arity, Signature, MetaWSpec, Fdoc)]
- end
+ {Type, Name} = func_to_atom(proplists:get_value(name,Attr)),
+ Arity = proplists:get_value(arity,Attr),
+ Signature = proplists:get_value(signature,Attr),
+ FMeta = proplists:get_value(meta,Attr),
+ MetaWSpec = add_spec(AST,FMeta),
+ MetaDepr
+ = case otp_internal:obsolete(Module, Name, Arity) of
+ {deprecated, Text} ->
+ MetaWSpec#{ deprecated =>
+ unicode:characters_to_binary(
+ erl_lint:format_error({deprecated,{Module,Name,Arity}, Text})) };
+ {deprecated, Replacement, Rel} ->
+ MetaWSpec#{ deprecated =>
+ unicode:characters_to_binary(
+ erl_lint:format_error({deprecated,{Module,Name,Arity}, Replacement, Rel})) };
+ _ -> MetaWSpec
+ end,
+ docs_v1_entry(Type, Anno, Name, Arity, Signature, MetaDepr, Fdoc)
end, Functions),
docs_v1(ModuleDocs, Anno, TypeMeta, FuncEntrys ++ TypeEntries).
@@ -691,6 +745,7 @@ docs_v1(DocContents, Anno, Metadata, Docs) ->
docs = Docs }.
docs_v1_entry(Kind, Anno, Name, Arity, Signature, Metadata, DocContents) ->
+
AnnoWLine =
case Metadata of
#{ signature := [Sig|_] } ->
@@ -699,8 +754,16 @@ docs_v1_entry(Kind, Anno, Name, Arity, Signature, Metadata, DocContents) ->
_NoSignature ->
Anno
end,
- {{Kind, Name, Arity}, AnnoWLine, lists:flatten(Signature),
- #{ <<"en">> => shell_docs:normalize(DocContents)}, Metadata}.
+
+ Doc =
+ case DocContents of
+ [] ->
+ #{};
+ DocContents ->
+ #{ <<"en">> => shell_docs:normalize(DocContents) }
+ end,
+
+ {{Kind, Name, Arity}, AnnoWLine, lists:flatten(Signature), Doc, Metadata}.
%% A special list_to_atom that handles
%% 'and'
@@ -708,11 +771,16 @@ docs_v1_entry(Kind, Anno, Name, Arity, Signature, Metadata, DocContents) ->
%% 'begin'
func_to_atom(List) ->
case erl_scan:string(List) of
- {ok,[{atom,_,Fn}],_} -> Fn;
- {ok,[{var,_,Fn}],_} -> Fn;
- {ok,[{Fn,_}],_} -> Fn;
- {ok,[{var,_,_},{':',_},_],_} ->
- callback
+ {ok,[{atom,_,Fn}],_} ->
+ {function, Fn};
+ {ok,[{var,_,Fn}],_} ->
+ {function, Fn};
+ {ok,[{Fn,_}],_} ->
+ {function, Fn};
+ {ok,[{var,_,_},{':',_},{atom,_,Fn}],_} ->
+ {callback, Fn};
+ {ok,[{var,_,_},{':',_},{var,_,Fn}],_} ->
+ {callback, Fn}
end.
-define(IS_TYPE(TO),(TO =:= type orelse TO =:= opaque)).
diff --git a/lib/erl_docgen/vsn.mk b/lib/erl_docgen/vsn.mk
index ebc9516da3..57b2fd10f4 100644
--- a/lib/erl_docgen/vsn.mk
+++ b/lib/erl_docgen/vsn.mk
@@ -1 +1 @@
-ERL_DOCGEN_VSN = 0.11
+ERL_DOCGEN_VSN = 1.0
diff --git a/lib/erl_interface/doc/src/ei.xml b/lib/erl_interface/doc/src/ei.xml
index 361021bf50..a47b344c4c 100644
--- a/lib/erl_interface/doc/src/ei.xml
+++ b/lib/erl_interface/doc/src/ei.xml
@@ -35,9 +35,6 @@
<lib>ei</lib>
<libsummary>Routines for handling the Erlang binary term format.</libsummary>
<description>
- <note><p>The support for VxWorks is deprecated as of OTP 22, and
- will be removed in OTP 23.</p></note>
-
<p>The library <c>ei</c> contains macros and functions to encode
and decode the Erlang binary term format.</p>
@@ -85,7 +82,9 @@
<p>There are also encode functions that use a dynamic buffer. It
is often more convenient to use these to encode data. All encode
functions comes in two versions; those starting with
- <c>ei_x</c> use a dynamic buffer.</p>
+ <c>ei_x_</c> use a dynamic buffer of type
+ <seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref>.
+ </p>
<p>All functions return <c>0</c> if successful, otherwise
<c>-1</c> (for example, if a term is not of the expected
@@ -93,7 +92,7 @@
<p>Some of the decode functions need a pre-allocated buffer. This
buffer must be allocated large enough, and for non-compound types
- the <c>ei_get_type()</c>
+ the <seecref marker="#ei_get_type"><c>ei_get_type()</c></seecref>
function returns the size required (notice that for strings an
extra byte is needed for the <c>NULL</c>-terminator).</p>
</description>
@@ -101,7 +100,55 @@
<section>
<title>Data Types</title>
<taglist>
- <tag><marker id="erlang_char_encoding"/>erlang_char_encoding</tag>
+ <tag><marker id="ei_term"/><c>ei_term</c></tag>
+ <item>
+ <code type="none">
+typedef struct {
+ char ei_type;
+ int arity;
+ int size;
+ union {
+ long i_val;
+ double d_val;
+ char atom_name[MAXATOMLEN_UTF8];
+ erlang_pid pid;
+ erlang_port port;
+ erlang_ref ref;
+ } value;
+} ei_term;</code>
+ <p>Structure written by
+ <seecref marker="#ei_decode_ei_term"><c>ei_decode_ei_term()</c></seecref>.
+ The <c>ei_type</c> field is the type of the term which equals to
+ what <seecref marker="#ei_get_type"><c>ei_get_type()</c></seecref>
+ sets <c>*type</c> to.
+ </p>
+ </item>
+ <tag><marker id="ei_x_buff"/><c>ei_x_buff</c></tag>
+ <item>
+ <p>A dynamically resized buffer. It is a <c>struct</c> with
+ two fields of interest for the user:
+ </p>
+ <taglist>
+ <tag><c>char *buff</c></tag>
+ <item>
+ <p>Pointer to the dynamically allocated buffer.</p>
+ </item>
+ <tag><c>int index</c></tag>
+ <item>
+ <p>Offset to the next byte to write which also equals the
+ amount of bytes currently written.</p>
+ </item>
+ </taglist>
+ <p>
+ An <c>ei_x_buff</c> is initialized by calling either
+ <seecref marker="#ei_x_new"><c>ei_x_new()</c></seecref> or
+ <seecref marker="#ei_x_new_with_version"><c>ei_x_new_with_version()</c></seecref>.
+ The memory used by an initialized <c>ei_x_buff</c> is released
+ by calling
+ <seecref marker="#ei_x_free"><c>ei_x_free()</c></seecref>.
+ </p>
+ </item>
+ <tag><marker id="erlang_char_encoding"/><c>erlang_char_encoding</c></tag>
<item>
<code type="none">
typedef enum {
@@ -117,11 +164,91 @@ typedef enum {
Notice that these constants are bit-flags and can be combined with
bitwise OR.</p>
</item>
+ <tag><marker id="erlang_fun"/><c>erlang_fun</c></tag>
+ <item>
+ <p>Opaque data type representing an Erlang fun.</p>
+ </item>
+ <tag><marker id="erlang_pid"/><c>erlang_pid</c></tag>
+ <item>
+ <p>Opaque data type representing an Erlang process identifier.</p>
+ </item>
+ <tag><marker id="erlang_port"/><c>erlang_port</c></tag>
+ <item>
+ <p>Opaque data type representing an Erlang port identifier.</p>
+ </item>
+ <tag><marker id="erlang_ref"/><c>erlang_ref</c></tag>
+ <item>
+ <p>Opaque data type representing an Erlang reference.</p>
+ </item>
+ <tag><marker id="erlang_trace"/><c>erlang_trace</c></tag>
+ <item>
+ <p>Opaque data type representing an Erlang sequential trace token.</p>
+ </item>
</taglist>
</section>
<funcs>
<func>
+ <name since="OTP 23.0"><ret>int</ret><nametext>ei_cmp_pids(erlang_pid *a, erlang_pid *b)</nametext></name>
+ <fsummary>Compare two pids.</fsummary>
+ <type>
+ <v><seecref marker="#erlang_pid"><c>erlang_pid</c></seecref></v>
+ </type>
+ <desc>
+ <p>
+ Compare two process identifiers. The comparison is done the same way
+ as Erlang does.
+ </p>
+ <p>
+ Returns <c>0</c> if <c>a</c> and <c>b</c> are equal. Returns a value
+ less than <c>0</c> if <c>a</c> compares as less than <c>b</c>.
+ Returns a value larger than <c>0</c> if <c>a</c> compares as larger
+ than <c>b</c>.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="OTP 23.0"><ret>int</ret><nametext>ei_cmp_ports(erlang_port *a, erlang_port *b)</nametext></name>
+ <fsummary>Compare two ports.</fsummary>
+ <type>
+ <v><seecref marker="#erlang_port"><c>erlang_port</c></seecref></v>
+ </type>
+ <desc>
+ <p>
+ Compare two port identifiers. The comparison is done the same way as
+ Erlang does.
+ </p>
+ <p>
+ Returns <c>0</c> if <c>a</c> and <c>b</c> are equal. Returns a value
+ less than <c>0</c> if <c>a</c> compares as less than <c>b</c>.
+ Returns a value larger than <c>0</c> if <c>a</c> compares as larger
+ than <c>b</c>.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="OTP 23.0"><ret>int</ret><nametext>ei_cmp_refs(erlang_ref *a, erlang_ref *b)</nametext></name>
+ <fsummary>Compare two references.</fsummary>
+ <type>
+ <v><seecref marker="#erlang_ref"><c>erlang_ref</c></seecref></v>
+ </type>
+ <desc>
+ <p>
+ Compare two references. The comparison is done the same way as Erlang
+ does.
+ </p>
+ <p>
+ Returns <c>0</c> if <c>a</c> and <c>b</c> are equal. Returns a value
+ less than <c>0</c> if <c>a</c> compares as less than <c>b</c>.
+ Returns a value larger than <c>0</c> if <c>a</c> compares as larger
+ than <c>b</c>.
+ </p>
+ </desc>
+ </func>
+
+ <func>
<name since=""><ret>int</ret><nametext>ei_decode_atom(const char *buf, int *index, char *p)</nametext></name>
<fsummary>Decode an atom.</fsummary>
<desc>
@@ -134,6 +261,9 @@ typedef enum {
<func>
<name since="OTP R16B"><ret>int</ret><nametext>ei_decode_atom_as(const char *buf, int *index, char *p, int plen, erlang_char_encoding want, erlang_char_encoding* was, erlang_char_encoding* result)</nametext></name>
<fsummary>Decode an atom.</fsummary>
+ <type>
+ <v><seecref marker="#erlang_char_encoding"><c>erlang_char_encoding</c></seecref></v>
+ </type>
<desc>
<p>Decodes an atom from the binary format. The <c>NULL</c>-terminated
name of the atom is placed in buffer at <c>p</c> of length <c>plen</c>
@@ -173,7 +303,8 @@ typedef enum {
<c>len</c> is set to the actual size of the
binary. Notice that <c>ei_decode_binary()</c> assumes that
there is enough room for the binary. The size required can be
- fetched by <c>ei_get_type()</c>.</p>
+ fetched by
+ <seecref marker="#ei_get_type"><c>ei_get_type()</c></seecref>.</p>
</desc>
</func>
@@ -249,6 +380,9 @@ typedef enum {
<func>
<name since=""><ret>int</ret><nametext>ei_decode_ei_term(const char* buf, int* index, ei_term* term)</nametext></name>
<fsummary>Decode a term, without previous knowledge of type.</fsummary>
+ <type>
+ <v><seecref marker="#ei_term"><c>ei_term</c></seecref></v>
+ </type>
<desc>
<p>Decodes any term, or at least tries to. If the term
pointed at by <c>*index</c> in <c>buf</c> fits
@@ -271,6 +405,9 @@ typedef enum {
<name since=""><ret>int</ret><nametext>ei_decode_fun(const char *buf, int *index, erlang_fun *p)</nametext></name>
<name since=""><ret>void</ret><nametext>free_fun(erlang_fun* f)</nametext></name>
<fsummary>Decode a fun.</fsummary>
+ <type>
+ <v><seecref marker="#erlang_fun"><c>erlang_fun</c></seecref></v>
+ </type>
<desc>
<p>Decodes a fun from the binary format. Parameter
<c>p</c> is to be <c>NULL</c> or point to an
@@ -283,6 +420,37 @@ typedef enum {
</func>
<func>
+ <name since="OTP 23.0"><ret>int</ret><nametext>ei_decode_iodata(const char *buf, int *index, int *size, char *outbuf)</nametext></name>
+ <fsummary>Decode iodata().</fsummary>
+ <desc>
+ <p>Decodes a term of the type <seeguide marker="system/reference_manual:typespec#builtin_types"><c>iodata()</c></seeguide>. The <c>iodata()</c> term will be
+ flattened an written into the buffer pointed to by the <c>outbuf</c>
+ argument. The byte size of the <c>iodata</c> is written into the
+ integer variable pointed to by the <c>size</c> argument. Both <c>size</c>
+ and <c>outbuf</c> can be set to <c>NULL</c>. The integer pointed to
+ by the <c>index</c> argument is updated to refer to the term
+ following after the <c>iodata()</c> term regardless of the the state
+ of the <c>size</c> and the <c>outbuf</c> arguments.
+ </p>
+ <p>Note that the buffer pointed to by the <c>outbuf</c> argument
+ must be large enough if a non <c>NULL</c> value is passed as
+ <c>outbuf</c>. You typically want to call <c>ei_decode_iodata()</c>
+ twice. First with a non <c>NULL</c> <c>size</c> argument and
+ a <c>NULL</c> <c>outbuf</c> argument in order to determine the
+ size of the buffer needed, and then once again in order to do
+ the actual decoding. Note that the integer pointed to by <c>index</c>
+ will be updated by the call determining the size as well, so you
+ need to reset it before the second call doing the actual decoding.
+ </p>
+ <p>Returns <c>0</c> on success and <c>-1</c> on failure. Failure
+ might be either due to invalid encoding of the term or due to
+ the term not being of the type <c>iodata()</c>. On failure, the
+ integer pointed to by the <c>index</c> argument will be updated
+ to refer to the sub term where the failure was detected.</p>
+ </desc>
+ </func>
+
+ <func>
<name since=""><ret>int</ret><nametext>ei_decode_list_header(const char *buf, int *index, int *arity)</nametext></name>
<fsummary>Decode a list.</fsummary>
<desc>
@@ -315,8 +483,7 @@ typedef enum {
<desc>
<p>Decodes a GCC <c>long long</c> or Visual C++
<c>__int64</c>
- (64-bit) integer from the binary format. This
- function is missing in the VxWorks port.</p>
+ (64-bit) integer from the binary format.</p>
</desc>
</func>
@@ -336,6 +503,9 @@ typedef enum {
<func>
<name since=""><ret>int</ret><nametext>ei_decode_pid(const char *buf, int *index, erlang_pid *p)</nametext></name>
<fsummary>Decode a <c>pid</c>.</fsummary>
+ <type>
+ <v><seecref marker="#erlang_pid"><c>erlang_pid</c></seecref></v>
+ </type>
<desc>
<p>Decodes a process identifier (pid) from the binary format.</p>
</desc>
@@ -344,6 +514,9 @@ typedef enum {
<func>
<name since=""><ret>int</ret><nametext>ei_decode_port(const char *buf, int *index, erlang_port *p)</nametext></name>
<fsummary>Decode a port.</fsummary>
+ <type>
+ <v><seecref marker="#erlang_port"><c>erlang_port</c></seecref></v>
+ </type>
<desc>
<p>Decodes a port identifier from the binary format.</p>
</desc>
@@ -352,6 +525,9 @@ typedef enum {
<func>
<name since=""><ret>int</ret><nametext>ei_decode_ref(const char *buf, int *index, erlang_ref *p)</nametext></name>
<fsummary>Decode a reference.</fsummary>
+ <type>
+ <v><seecref marker="#erlang_ref"><c>erlang_ref</c></seecref></v>
+ </type>
<desc>
<p>Decodes a reference from the binary format.</p>
</desc>
@@ -375,6 +551,9 @@ typedef enum {
<func>
<name since=""><ret>int</ret><nametext>ei_decode_trace(const char *buf, int *index, erlang_trace *p)</nametext></name>
<fsummary>Decode a trace token.</fsummary>
+ <type>
+ <v><seecref marker="#erlang_trace"><c>erlang_trace</c></seecref></v>
+ </type>
<desc>
<p>Decodes an Erlang trace token from the binary format.</p>
</desc>
@@ -406,7 +585,7 @@ typedef enum {
<desc>
<p>Decodes a GCC <c>unsigned long long</c> or Visual C++
<c>unsigned __int64</c> (64-bit) integer from the binary
- format. This function is missing in the VxWorks port.</p>
+ format.</p>
</desc>
</func>
@@ -426,6 +605,9 @@ typedef enum {
<name since=""><ret>int</ret><nametext>ei_x_encode_atom(ei_x_buff* x, const char *p)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_atom_len(ei_x_buff* x, const char *p, int len)</nametext></name>
<fsummary>Encode an atom.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes an atom in the binary format. Parameter <c>p</c>
is the name of the atom in Latin-1 encoding. Only up to
@@ -441,6 +623,10 @@ typedef enum {
<name since="OTP R16B"><ret>int</ret><nametext>ei_x_encode_atom_as(ei_x_buff* x, const char *p, erlang_char_encoding from_enc, erlang_char_encoding to_enc)</nametext></name>
<name since="OTP R16B"><ret>int</ret><nametext>ei_x_encode_atom_len_as(ei_x_buff* x, const char *p, int len, erlang_char_encoding from_enc, erlang_char_encoding to_enc)</nametext></name>
<fsummary>Encode an atom.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ <v><seecref marker="#erlang_char_encoding"><c>erlang_char_encoding</c></seecref></v>
+ </type>
<desc>
<p>Encodes an atom in the binary format. Parameter <c>p</c> is the name of the atom with
character encoding
@@ -459,6 +645,9 @@ typedef enum {
<name since=""><ret>int</ret><nametext>ei_encode_bignum(char *buf, int *index, mpz_t obj)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_bignum(ei_x_buff *x, mpz_t obj)</nametext></name>
<fsummary>Encode an arbitrary precision integer.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes a GMP <c>mpz_t</c> integer to binary format.
To use this function, the <c>ei</c> library must be configured and
@@ -470,6 +659,9 @@ typedef enum {
<name since=""><ret>int</ret><nametext>ei_encode_binary(char *buf, int *index, const void *p, long len)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_binary(ei_x_buff* x, const void *p, long len)</nametext></name>
<fsummary>Encode a binary.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes a binary in the binary format. The data is at
<c>p</c>, of <c>len</c> bytes length.</p>
@@ -482,6 +674,9 @@ typedef enum {
<name since="OTP 22.0"><ret>int</ret>
<nametext>ei_x_encode_bitstring(ei_x_buff* x, const char *p, size_t bitoffs, size_t nbits)</nametext></name>
<fsummary>Encode a bitstring.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes a bit string in the binary format.</p>
<p>The data is at <c>p</c>. The length of the bit string is <c>nbits</c>
@@ -502,6 +697,9 @@ typedef enum {
<name since=""><ret>int</ret><nametext>ei_encode_boolean(char *buf, int *index, int p)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_boolean(ei_x_buff* x, int p)</nametext></name>
<fsummary>Encode a boolean.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes a boolean value as the atom <c>true</c> if
<c>p</c> is not zero, or <c>false</c> if <c>p</c> is
@@ -513,6 +711,9 @@ typedef enum {
<name since=""><ret>int</ret><nametext>ei_encode_char(char *buf, int *index, char p)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_char(ei_x_buff* x, char p)</nametext></name>
<fsummary>Encode an 8-bit integer between 0-255.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes a char (8-bit) as an integer between 0-255 in the binary
format. For historical reasons the integer argument is of
@@ -527,6 +728,9 @@ typedef enum {
<name since=""><ret>int</ret><nametext>ei_encode_double(char *buf, int *index, double p)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_double(ei_x_buff* x, double p)</nametext></name>
<fsummary>Encode a double float.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes a double-precision (64-bit) floating point number in
the binary format.</p>
@@ -539,6 +743,9 @@ typedef enum {
<name since=""><ret>int</ret><nametext>ei_encode_empty_list(char* buf, int* index)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_empty_list(ei_x_buff* x)</nametext></name>
<fsummary>Encode an empty list (<c>nil</c>).</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes an empty list. It is often used at the tail of a list.</p>
</desc>
@@ -548,6 +755,10 @@ typedef enum {
<name since=""><ret>int</ret><nametext>ei_encode_fun(char *buf, int *index, const erlang_fun *p)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_fun(ei_x_buff* x, const erlang_fun* fun)</nametext></name>
<fsummary>Encode a fun.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ <v><seecref marker="#erlang_fun"><c>erlang_fun</c></seecref></v>
+ </type>
<desc>
<p>Encodes a fun in the binary format. Parameter <c>p</c>
points to an <c>erlang_fun</c> structure. The
@@ -561,6 +772,9 @@ typedef enum {
<name since=""><ret>int</ret><nametext>ei_encode_list_header(char *buf, int *index, int arity)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_list_header(ei_x_buff* x, int arity)</nametext></name>
<fsummary>Encode a list.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes a list header, with a specified
arity. The next <c>arity+1</c> terms are the elements
@@ -598,6 +812,9 @@ ei_x_encode_empty_list(&amp;x);</pre>
<name since=""><ret>int</ret><nametext>ei_encode_long(char *buf, int *index, long p)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_long(ei_x_buff* x, long p)</nametext></name>
<fsummary>Encode integer.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes a long integer in the binary format.
If the code is 64 bits, the function <c>ei_encode_long()</c> is
@@ -609,10 +826,12 @@ ei_x_encode_empty_list(&amp;x);</pre>
<name since=""><ret>int</ret><nametext>ei_encode_longlong(char *buf, int *index, long long p)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_longlong(ei_x_buff* x, long long p)</nametext></name>
<fsummary>Encode integer.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes a GCC <c>long long</c> or Visual C++
- <c>__int64</c> (64-bit) integer in the binary format.
- This function is missing in the VxWorks port.</p>
+ <c>__int64</c> (64-bit) integer in the binary format.</p>
</desc>
</func>
@@ -620,6 +839,9 @@ ei_x_encode_empty_list(&amp;x);</pre>
<name since="OTP 17.0"><ret>int</ret><nametext>ei_encode_map_header(char *buf, int *index, int arity)</nametext></name>
<name since="OTP 17.0"><ret>int</ret><nametext>ei_x_encode_map_header(ei_x_buff* x, int arity)</nametext></name>
<fsummary>Encode a map.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes a map header, with a specified arity. The next
<c>arity*2</c> terms encoded will be the keys and values of the map
@@ -641,11 +863,20 @@ ei_x_encode_string(&amp;x, "Banana");</pre>
<name since=""><ret>int</ret><nametext>ei_encode_pid(char *buf, int *index, const erlang_pid *p)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_pid(ei_x_buff* x, const erlang_pid *p)</nametext></name>
<fsummary>Encode a pid.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ <v><seecref marker="#erlang_pid"><c>erlang_pid</c></seecref></v>
+ </type>
<desc>
<p>Encodes an Erlang process identifier (pid) in the binary
format. Parameter <c>p</c> points to an
- <c>erlang_pid</c> structure (which should have been
- obtained earlier with <c>ei_decode_pid()</c>).</p>
+ <c>erlang_pid</c> structure which should either have been
+ obtained earlier with
+ <seecref marker="#ei_decode_pid"><c>ei_decode_pid()</c></seecref>,
+ <seecref marker="ei_connect#ei_self"><c>ei_self()</c></seecref> or
+ created by
+ <seecref marker="ei_connect#ei_make_pid"><c>ei_make_pid()</c></seecref>.
+ </p>
</desc>
</func>
@@ -653,11 +884,16 @@ ei_x_encode_string(&amp;x, "Banana");</pre>
<name since=""><ret>int</ret><nametext>ei_encode_port(char *buf, int *index, const erlang_port *p)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_port(ei_x_buff* x, const erlang_port *p)</nametext></name>
<fsummary>Encode a port.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ <v><seecref marker="#erlang_port"><c>erlang_port</c></seecref></v>
+ </type>
<desc>
<p>Encodes an Erlang port in the binary format. Parameter
- <c>p</c> points to a <c>erlang_port</c>
- structure (which should have been obtained earlier with
- <c>ei_decode_port()</c>).</p>
+ <c>p</c> points to an <c>erlang_port</c> structure which
+ should have been obtained earlier with
+ <seecref marker="#ei_decode_port"><c>ei_decode_port()</c></seecref>,
+ </p>
</desc>
</func>
@@ -665,11 +901,17 @@ ei_x_encode_string(&amp;x, "Banana");</pre>
<name since=""><ret>int</ret><nametext>ei_encode_ref(char *buf, int *index, const erlang_ref *p)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_ref(ei_x_buff* x, const erlang_ref *p)</nametext></name>
<fsummary>Encode a ref.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ <v><seecref marker="#erlang_ref"><c>erlang_ref</c></seecref></v>
+ </type>
<desc>
<p>Encodes an Erlang reference in the binary format. Parameter
- <c>p</c> points to a <c>erlang_ref</c>
- structure (which should have been obtained earlier with
- <c>ei_decode_ref()</c>).</p>
+ <c>p</c> points to an <c>erlang_ref</c>
+ structure which either should have been obtained earlier with
+ <seecref marker="#ei_decode_ref"><c>ei_decode_ref()</c></seecref>, or
+ created by
+ <seecref marker="ei_connect#ei_make_ref"><c>ei_make_ref()</c></seecref>.</p>
</desc>
</func>
@@ -679,6 +921,9 @@ ei_x_encode_string(&amp;x, "Banana");</pre>
<name since=""><ret>int</ret><nametext>ei_x_encode_string(ei_x_buff* x, const char *p)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_string_len(ei_x_buff* x, const char* s, int len)</nametext></name>
<fsummary>Encode a string.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes a string in the binary format. (A string in Erlang
is a list, but is encoded as a character array in the binary
@@ -690,11 +935,16 @@ ei_x_encode_string(&amp;x, "Banana");</pre>
<name since=""><ret>int</ret><nametext>ei_encode_trace(char *buf, int *index, const erlang_trace *p)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_trace(ei_x_buff* x, const erlang_trace *p)</nametext></name>
<fsummary>Encode a trace token.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ <v><seecref marker="#erlang_trace"><c>erlang_trace</c></seecref></v>
+ </type>
<desc>
<p>Encodes an Erlang trace token in the binary format.
Parameter <c>p</c> points to a
- <c>erlang_trace</c> structure (which should have been
- obtained earlier with <c>ei_decode_trace()</c>).</p>
+ <c>erlang_trace</c> structure which should have been
+ obtained earlier with
+ <seecref marker="#ei_decode_trace"><c>ei_decode_trace()</c></seecref>.</p>
</desc>
</func>
@@ -702,6 +952,9 @@ ei_x_encode_string(&amp;x, "Banana");</pre>
<name since=""><ret>int</ret><nametext>ei_encode_tuple_header(char *buf, int *index, int arity)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_tuple_header(ei_x_buff* x, int arity)</nametext></name>
<fsummary>Encode a tuple.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes a tuple header, with a specified
arity. The next <c>arity</c> terms encoded will be the
@@ -721,6 +974,9 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
<name since=""><ret>int</ret><nametext>ei_encode_ulong(char *buf, int *index, unsigned long p)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_ulong(ei_x_buff* x, unsigned long p)</nametext></name>
<fsummary>Encode unsigned integer.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes an unsigned long integer in the binary format.
If the code is 64 bits, the function <c>ei_encode_ulong()</c> is
@@ -732,10 +988,13 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
<name since=""><ret>int</ret><nametext>ei_encode_ulonglong(char *buf, int *index, unsigned long long p)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_ulonglong(ei_x_buff* x, unsigned long long p)</nametext></name>
<fsummary>Encode unsigned integer.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes a GCC <c>unsigned long long</c> or Visual C++
<c>unsigned __int64</c> (64-bit) integer in the binary
- format. This function is missing in the VxWorks port.</p>
+ format.</p>
</desc>
</func>
@@ -743,6 +1002,9 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
<name since=""><ret>int</ret><nametext>ei_encode_version(char *buf, int *index)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_version(ei_x_buff* x)</nametext></name>
<fsummary>Encode version.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes a version magic number for the binary format. Must
be the first token in a binary term.</p>
@@ -760,6 +1022,105 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
the number of bytes. For lists, tuples and maps, <c>*size</c> is the
arity of the object. For other types, <c>*size</c> is 0. In all
cases, <c>index</c> is left unchanged.</p>
+ <p>Currently <c>*type</c> is one of:</p>
+ <taglist>
+ <tag>ERL_ATOM_EXT</tag>
+ <item><p>
+ Decode using either
+ <seecref marker="#ei_decode_atom"><c>ei_decode_atom()</c></seecref>,
+ <seecref marker="#ei_decode_atom_as"><c>ei_decode_atom_as()</c></seecref>,
+ or
+ <seecref marker="#ei_decode_boolean"><c>ei_decode_boolean()</c></seecref>.
+ </p></item>
+
+ <tag>ERL_BINARY_EXT</tag>
+ <item><p>
+ Decode using either
+ <seecref marker="#ei_decode_binary"><c>ei_decode_binary()</c></seecref>,
+ <seecref marker="#ei_decode_bitstring"><c>ei_decode_bitstring()</c></seecref>,
+ or
+ <seecref marker="#ei_decode_iodata"><c>ei_decode_iodata()</c></seecref>.
+ </p></item>
+
+ <tag>ERL_BIT_BINARY_EXT</tag>
+ <item><p>
+ Decode using
+ <seecref marker="#ei_decode_bitstring"><c>ei_decode_bitstring()</c></seecref>.
+ </p></item>
+
+ <tag>ERL_FLOAT_EXT</tag>
+ <item><p>
+ Decode using
+ <seecref marker="#ei_decode_double"><c>ei_decode_double()</c></seecref>.
+ </p></item>
+
+ <tag>ERL_NEW_FUN_EXT<br/>ERL_FUN_EXT<br/>ERL_EXPORT_EXT</tag>
+ <item><p>
+ Decode using
+ <seecref marker="#ei_decode_fun"><c>ei_decode_fun()</c></seecref>.
+ </p></item>
+
+ <tag>ERL_SMALL_INTEGER_EXT<br/>ERL_INTEGER_EXT<br/>ERL_SMALL_BIG_EXT<br/>ERL_LARGE_BIG_EXT</tag>
+ <item><p>
+ Decode using either
+ <seecref marker="#ei_decode_char"><c>ei_decode_char()</c></seecref>,
+ <seecref marker="#ei_decode_long"><c>ei_decode_long()</c></seecref>,
+ <seecref marker="#ei_decode_longlong"><c>ei_decode_longlong()</c></seecref>,
+ <seecref marker="#ei_decode_ulong"><c>ei_decode_ulong()</c></seecref>,
+ <seecref marker="#ei_decode_ulonglong"><c>ei_decode_ulonglong()</c></seecref>,
+ or
+ <seecref marker="#ei_decode_bignum"><c>ei_decode_bignum()</c></seecref>.
+ </p></item>
+
+ <tag>ERL_LIST_EXT<br/>ERL_NIL_EXT</tag>
+ <item><p>
+ Decode using either
+ <seecref marker="#ei_decode_list_header"><c>ei_decode_list_header()</c></seecref>,
+ or
+ <seecref marker="#ei_decode_iodata"><c>ei_decode_iodata()</c></seecref>.
+ </p></item>
+
+ <tag>ERL_STRING_EXT</tag>
+ <item><p>
+ Decode using either
+ <seecref marker="#ei_decode_string"><c>ei_decode_string()</c></seecref>,
+ or
+ <seecref marker="#ei_decode_iodata"><c>ei_decode_iodata()</c></seecref>.
+ </p></item>
+
+ <tag>ERL_MAP_EXT</tag>
+ <item><p>
+ Decode using
+ <seecref marker="#ei_decode_map_header"><c>ei_decode_map_header()</c></seecref>.
+ </p></item>
+
+ <tag>ERL_PID_EXT</tag>
+ <item><p>
+ Decode using
+ <seecref marker="#ei_decode_pid"><c>ei_decode_pid()</c></seecref>.
+ </p></item>
+
+ <tag>ERL_PORT_EXT</tag>
+ <item><p>
+ Decode using
+ <seecref marker="#ei_decode_port"><c>ei_decode_port()</c></seecref>.
+ </p></item>
+
+ <tag>ERL_NEW_REFERENCE_EXT</tag>
+ <item><p>
+ Decode using
+ <seecref marker="#ei_decode_ref"><c>ei_decode_ref()</c></seecref>.
+ </p></item>
+
+ <tag>ERL_SMALL_TUPLE_EXT<br/>ERL_LARGE_TUPLE_EXT</tag>
+ <item><p>
+ Decode using
+ <seecref marker="#ei_decode_tuple_header"><c>ei_decode_tuple_header()</c></seecref>.
+ </p></item>
+ </taglist>
+ <p>Instead of decoding a term you can also skipped past it if you are
+ not interested in the data by usage of
+ <seecref marker="#ei_skip_term"><c>ei_skip_term()</c></seecref>.</p>
</desc>
</func>
@@ -801,11 +1162,8 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
</func>
<func>
- <name since=""><ret>void</ret><nametext>ei_set_compat_rel(release_number)</nametext></name>
+ <name since=""><ret>void</ret><nametext>ei_set_compat_rel(unsigned release_number)</nametext></name>
<fsummary>Set the ei library in compatibility mode.</fsummary>
- <type>
- <v>unsigned release_number;</v>
- </type>
<desc>
<marker id="ei_set_compat_rel"></marker>
<p>In general, the <c>ei</c> library is guaranteed
@@ -878,6 +1236,9 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
<name since=""><ret>int</ret><nametext>ei_x_append(ei_x_buff* x, const ei_x_buff* x2)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_append_buf(ei_x_buff* x, const char* buf, int len)</nametext></name>
<fsummary>Append a buffer at the end.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Appends data at the end of buffer <c>x</c>.</p>
</desc>
@@ -887,6 +1248,10 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
<name since=""><ret>int</ret><nametext>ei_x_format(ei_x_buff* x, const char* fmt, ...)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_format_wo_ver(ei_x_buff* x, const char *fmt, ... )</nametext></name>
<fsummary>Format a term from a format string and parameters.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ <v><seecref marker="#erlang_pid"><c>erlang_pid</c></seecref></v>
+ </type>
<desc>
<p>Formats a term, given as a string, to a buffer.
Works like a sprintf for Erlang terms.
@@ -915,9 +1280,13 @@ encodes the tuple {numbers,12,3.14159}</pre>
<func>
<name since=""><ret>int</ret><nametext>ei_x_free(ei_x_buff* x)</nametext></name>
<fsummary>Free a buffer.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
- <p>Frees an <c>ei_x_buff</c> buffer.
- The memory used by the buffer is returned to the OS.</p>
+ <p>Deallocates the dynamically allocated content of the buffer
+ referred by <c>x</c>. After deallocation, the <c>buff</c> field
+ is set to <c>NULL</c>.</p>
</desc>
</func>
@@ -925,10 +1294,14 @@ encodes the tuple {numbers,12,3.14159}</pre>
<name since=""><ret>int</ret><nametext>ei_x_new(ei_x_buff* x)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_new_with_version(ei_x_buff* x)</nametext></name>
<fsummary>Allocate a new buffer.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
- <p>Allocates a new <c>ei_x_buff</c> buffer. The
- fields of the structure pointed to by parameter <c>x</c>
- is filled in, and a default buffer is allocated.
+ <p>Initialize the dynamically realizable buffer referred to
+ by <c>x</c>. The fields of the structure pointed to by
+ parameter <c>x</c> is filled in, and a default buffer is
+ allocated.
<c>ei_x_new_with_version()</c> also puts an initial
version byte, which is used in the binary format (so that
<c>ei_x_encode_version()</c> will not be needed.)</p>
diff --git a/lib/erl_interface/doc/src/ei_connect.xml b/lib/erl_interface/doc/src/ei_connect.xml
index f991165df7..c5ef9440c5 100644
--- a/lib/erl_interface/doc/src/ei_connect.xml
+++ b/lib/erl_interface/doc/src/ei_connect.xml
@@ -34,9 +34,6 @@
<lib>ei_connect</lib>
<libsummary>Communicate with distributed Erlang.</libsummary>
<description>
- <note><p>The support for VxWorks is deprecated as of OTP 22, and
- will be removed in OTP 23.</p></note>
-
<p>This module enables C-programs to communicate with Erlang nodes,
using the Erlang distribution over TCP/IP.</p>
@@ -112,27 +109,10 @@
only supports IPv4. That is, at this time <c>addr</c> always
points to a <c>struct sockaddr_in</c> structure.</p>
- <p>The <c>ei_socket_callbacks</c> structure may be enlarged in
- the future. All fields not set, <em>needs</em> to be zeroed out.</p>
-
- <marker id="ei_socket_callbacks"/>
- <code type="none"><![CDATA[
-typedef struct {
- int flags;
- int (*socket)(void **ctx, void *setup_ctx);
- int (*close)(void *ctx);
- int (*listen)(void *ctx, void *addr, int *len, int backlog);
- int (*accept)(void **ctx, void *addr, int *len, unsigned tmo);
- int (*connect)(void *ctx, void *addr, int len, unsigned tmo);
- int (*writev)(void *ctx, const void *iov, int iovcnt, ssize_t *len, unsigned tmo);
- int (*write)(void *ctx, const char *buf, ssize_t *len, unsigned tmo);
- int (*read)(void *ctx, char *buf, ssize_t *len, unsigned tmo);
- int (*handshake_packet_header_size)(void *ctx, int *sz);
- int (*connect_handshake_complete)(void *ctx);
- int (*accept_handshake_complete)(void *ctx);
- int (*get_fd)(void *ctx, int *fd);
-} ei_socket_callbacks;
- ]]></code>
+ <p><marker id="ei_socket_callbacks_fields"/>The
+ <seecref marker="#ei_socket_callbacks"><c>ei_socket_callbacks</c></seecref>
+ structure may be enlarged in the future. All fields not set, <em>needs</em>
+ to be zeroed out. Currently the following fields exist:</p>
<taglist>
@@ -355,6 +335,79 @@ typedef struct {
</item>
</taglist>
</section>
+ <section>
+ <title>Data Types</title>
+ <taglist>
+ <tag><marker id="ei_cnode"/><c>ei_cnode</c></tag>
+ <item><p>
+ Opaque data type representing a C-node. A <c>ei_cnode</c>
+ structure is initialized by calling
+ <seecref marker="#ei_connect_init"><c>ei_connect_init()</c></seecref>
+ or friends.
+ </p></item>
+
+ <tag><marker id="ei_socket_callbacks"/><c>ei_socket_callbacks</c></tag>
+ <item><code type="none">
+typedef struct {
+ int flags;
+ int (*socket)(void **ctx, void *setup_ctx);
+ int (*close)(void *ctx);
+ int (*listen)(void *ctx, void *addr, int *len, int backlog);
+ int (*accept)(void **ctx, void *addr, int *len, unsigned tmo);
+ int (*connect)(void *ctx, void *addr, int len, unsigned tmo);
+ int (*writev)(void *ctx, const void *iov, int iovcnt, ssize_t *len, unsigned tmo);
+ int (*write)(void *ctx, const char *buf, ssize_t *len, unsigned tmo);
+ int (*read)(void *ctx, char *buf, ssize_t *len, unsigned tmo);
+ int (*handshake_packet_header_size)(void *ctx, int *sz);
+ int (*connect_handshake_complete)(void *ctx);
+ int (*accept_handshake_complete)(void *ctx);
+ int (*get_fd)(void *ctx, int *fd);
+} ei_socket_callbacks;</code>
+ <p>
+ Callbacks functions for a <seecref marker="#ussi"><i>User
+ Supplied Socket Implementation</i></seecref>.
+ <seecref marker="#ei_socket_callbacks_fields">Documentation
+ of each field</seecref> can be found in the
+ <i>User Supplied Socket Implementation</i> section
+ above.
+ </p>
+
+ </item>
+
+ <tag><marker id="ErlConnect"/><c>ErlConnect</c></tag>
+ <item><code type="none">
+typedef struct {
+ char ipadr[4]; /* Ip v4 address in network byte order */
+ char nodename[MAXNODELEN];
+} ErlConnect;</code>
+ <p>IP v4 address and nodename.</p>
+ </item>
+
+ <tag><marker id="Erl_IpAddr"/><c>Erl_IpAddr</c></tag>
+ <item><code type="none">
+typedef struct {
+ unsigned s_addr; /* Ip v4 address in network byte order */
+} Erl_IpAddr;</code>
+ <p>IP v4 address.</p>
+ </item>
+
+ <tag><marker id="erlang_msg"/><c>erlang_msg</c></tag>
+ <item>
+ <code type="none">
+typedef struct {
+ long msgtype;
+ erlang_pid from;
+ erlang_pid to;
+ char toname[MAXATOMLEN+1];
+ char cookie[MAXATOMLEN+1];
+ erlang_trace token;
+} erlang_msg;</code>
+ <p>Information about a message received via
+ <seecref marker="#ei_receive_msg"><c>ei_receive_msg()</c></seecref>
+ or friends.</p>
+ </item>
+ </taglist>
+ </section>
<funcs>
<func>
<name since=""><ret>struct hostent *</ret><nametext>ei_gethostbyaddr(const char *addr, int len, int type)</nametext></name>
@@ -371,6 +424,10 @@ typedef struct {
<func>
<name since=""><ret>int</ret><nametext>ei_accept(ei_cnode *ec, int listensock, ErlConnect *conp)</nametext></name>
<fsummary>Accept a connection from another node.</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ <v><seecref marker="#ErlConnect"><c>ErlConnect</c></seecref></v>
+ </type>
<desc>
<p>Used by a server process to accept a
connection from a client process.</p>
@@ -384,13 +441,8 @@ typedef struct {
</item>
<item>
<p><c>conp</c> is a pointer to an
- <c>ErlConnect</c> struct, described as follows:</p>
- <code type="none"><![CDATA[
-typedef struct {
- char ipadr[4];
- char nodename[MAXNODELEN];
-} ErlConnect;
- ]]></code>
+ <seecref marker="#ErlConnect"><c>ErlConnect</c></seecref>
+ struct.</p>
</item>
</list>
<p>On success, <c>conp</c> is filled in with the address and
@@ -404,6 +456,10 @@ typedef struct {
<name since=""><ret>int</ret><nametext>ei_accept_tmo(ei_cnode *ec, int listensock, ErlConnect *conp, unsigned timeout_ms)</nametext></name>
<fsummary>Accept a connection from another node with optional
time-out.</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ <v><seecref marker="#ErlConnect"><c>ErlConnect</c></seecref></v>
+ </type>
<desc>
<p>Equivalent to
<c>ei_accept</c> with an optional time-out argument,
@@ -422,9 +478,13 @@ typedef struct {
<func>
<name since=""><ret>int</ret><nametext>ei_connect(ei_cnode* ec, char *nodename)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_xconnect(ei_cnode* ec, Erl_IpAddr adr, char *alivename)</nametext></name>
- <name since="OTP @OTP-16251@"><ret>int</ret><nametext>ei_connect_host_port(ei_cnode* ec, char *hostname, int port)</nametext></name>
- <name since="OTP @OTP-16251@"><ret>int</ret><nametext>ei_xconnect_host_port(ei_cnode* ec, Erl_IpAddr adr, int port)</nametext></name>
+ <name since="OTP 23.0"><ret>int</ret><nametext>ei_connect_host_port(ei_cnode* ec, char *hostname, int port)</nametext></name>
+ <name since="OTP 23.0"><ret>int</ret><nametext>ei_xconnect_host_port(ei_cnode* ec, Erl_IpAddr adr, int port)</nametext></name>
<fsummary>Establish a connection to an Erlang node.</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ <v><seecref marker="#Erl_IpAddr"><c>Erl_IpAddr</c></seecref></v>
+ </type>
<desc>
<p>Sets up a connection to an Erlang node.</p>
<p><c>ei_xconnect()</c> requires the IP address of the
@@ -486,6 +546,11 @@ fd = ei_xconnect(&ec, &addr, ALIVE);
<name since=""><ret>int</ret><nametext>ei_connect_xinit(ei_cnode* ec, const char *thishostname, const char *thisalivename, const char *thisnodename, Erl_IpAddr thisipaddr, const char *cookie, short creation)</nametext></name>
<name since="OTP 21.3"><ret>int</ret><nametext>ei_connect_xinit_ussi(ei_cnode* ec, const char *thishostname, const char *thisalivename, const char *thisnodename, Erl_IpAddr thisipaddr, const char *cookie, short creation, ei_socket_callbacks *cbs, int cbs_sz, void *setup_context)</nametext></name>
<fsummary>Initialize for a connection.</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ <v><seecref marker="#Erl_IpAddr"><c>Erl_IpAddr</c></seecref></v>
+ <v><seecref marker="#ei_socket_callbacks"><c>ei_socket_callbacks</c></seecref></v>
+ </type>
<desc>
<p>Initializes the <c>ec</c> structure, to
identify the node name and cookie of the server. One of them
@@ -583,10 +648,14 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
<func>
<name since=""><ret>int</ret><nametext>ei_connect_tmo(ei_cnode* ec, char *nodename, unsigned timeout_ms)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_xconnect_tmo(ei_cnode* ec, Erl_IpAddr adr, char *alivename, unsigned timeout_ms)</nametext></name>
- <name since="OTP @OTP-16251@"><ret>int</ret><nametext>ei_connect_host_port_tmo(ei_cnode* ec, char *hostname, int port, unsigned ms)</nametext></name>
- <name since="OTP @OTP-16251@"><ret>int</ret><nametext>ei_xconnect_host_port_tmo(ei_cnode* ec, Erl_IpAddr adr, int port, unsigned ms)</nametext></name>
+ <name since="OTP 23.0"><ret>int</ret><nametext>ei_connect_host_port_tmo(ei_cnode* ec, char *hostname, int port, unsigned ms)</nametext></name>
+ <name since="OTP 23.0"><ret>int</ret><nametext>ei_xconnect_host_port_tmo(ei_cnode* ec, Erl_IpAddr adr, int port, unsigned ms)</nametext></name>
<fsummary>Establish a connection to an Erlang node with optional
time-out.</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ <v><seecref marker="#Erl_IpAddr"><c>Erl_IpAddr</c></seecref></v>
+ </type>
<desc>
<p>Equivalent to <c>ei_connect</c>, <c>ei_xconnect</c>,
<c>ei_connect_host_port</c> and
@@ -613,6 +682,10 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
<name since="OTP 21.3"><ret>int</ret><nametext>ei_listen(ei_cnode *ec, int *port, int backlog)</nametext></name>
<name since="OTP 21.3"><ret>int</ret><nametext>ei_xlisten(ei_cnode *ec, Erl_IpAddr adr, int *port, int backlog)</nametext></name>
<fsummary>Create a listen socket.</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ <v><seecref marker="#Erl_IpAddr"><c>Erl_IpAddr</c></seecref></v>
+ </type>
<desc>
<p>Used by a server process to setup a listen socket which
later can be used for accepting connections from client processes.
@@ -649,8 +722,60 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
</func>
<func>
+ <name since="OTP 23.0"><ret>int</ret><nametext>ei_make_pid(ei_cnode *ec, erlang_pid *pid)</nametext></name>
+ <fsummary>Create a new process identifier</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ <v><seecref marker="ei#erlang_pid"><c>erlang_pid</c></seecref></v>
+ </type>
+ <desc>
+ <p>
+ Creates a new process identifier in the argument <c>pid</c>. This process identifier
+ refers to a conseptual process residing on the C-node identified by the argument
+ <c>ec</c>. On success <c>0</c> is returned. On failure <c>ERL_ERROR</c> is
+ returned and <c>erl_errno</c> is set.
+ </p>
+ <p>
+ The C-node identified by <c>ec</c> must have been initialized and must have
+ received a name prior to the call to <c>ei_make_pid()</c>. Initialization
+ of the C-node is done by a call to
+ <seecref marker="#ei_connect_init"><c>ei_connect_init()</c></seecref>
+ or friends. If the name is dynamically assigned from the peer node, the
+ C-node also has to be connected.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="OTP 23.0"><ret>int</ret><nametext>ei_make_ref(ei_cnode *ec, erlang_ref *ref)</nametext></name>
+ <fsummary>Create a new reference</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ <v><seecref marker="ei#erlang_ref"><c>erlang_ref</c></seecref></v>
+ </type>
+ <desc>
+ <p>
+ Creates a new reference in the argument <c>ref</c>. This reference originates
+ from the C-node identified by the argument <c>ec</c>. On success <c>0</c> is
+ returned. On failure <c>ERL_ERROR</c> is returned and <c>erl_errno</c> is set.
+ </p>
+ <p>
+ The C-node identified by <c>ec</c> must have been initialized and must have
+ received a name prior to the call to <c>ei_make_ref()</c>. Initialization
+ of the C-node is done by a call to
+ <seecref marker="#ei_connect_init"><c>ei_connect_init()</c></seecref>
+ or friends. If the name is dynamically assigned from the peer node, the
+ C-node also has to be connected.
+ </p>
+ </desc>
+ </func>
+
+ <func>
<name since=""><ret>int</ret><nametext>ei_publish(ei_cnode *ec, int port)</nametext></name>
<fsummary>Publish a node name.</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ </type>
<desc>
<p>Used by a server process to register
with the local name server EPMD, thereby allowing
@@ -688,6 +813,9 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
<func>
<name since=""><ret>int</ret><nametext>ei_publish_tmo(ei_cnode *ec, int port, unsigned timeout_ms)</nametext></name>
<fsummary>Publish a node name with optional time-out.</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ </type>
<desc>
<p>Equivalent to
<c>ei_publish</c> with an optional time-out argument,
@@ -739,6 +867,9 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
<func>
<name since=""><ret>int</ret><nametext>ei_receive_encoded(int fd, char **mbufp, int *bufsz, erlang_msg *msg, int *msglen)</nametext></name>
<fsummary>Obsolete function for receiving a message.</fsummary>
+ <type>
+ <v><seecref marker="#erlang_msg"><c>erlang_msg</c></seecref></v>
+ </type>
<desc>
<p>This function is retained for compatibility with code
generated by the interface compiler and with code following
@@ -770,6 +901,9 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
<name since=""><ret>int</ret><nametext>ei_receive_encoded_tmo(int fd, char **mbufp, int *bufsz, erlang_msg *msg, int *msglen, unsigned timeout_ms)</nametext></name>
<fsummary>Obsolete function for receiving a message with time-out.
</fsummary>
+ <type>
+ <v><seecref marker="#erlang_msg"><c>erlang_msg</c></seecref></v>
+ </type>
<desc>
<p>Equivalent to
<c>ei_receive_encoded</c> with an optional time-out argument,
@@ -781,6 +915,10 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
<name since=""><ret>int</ret><nametext>ei_receive_msg(int fd, erlang_msg* msg, ei_x_buff* x)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_xreceive_msg(int fd, erlang_msg* msg, ei_x_buff* x)</nametext></name>
<fsummary>Receive a message.</fsummary>
+ <type>
+ <v><seecref marker="ei#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ <v><seecref marker="#erlang_msg"><c>erlang_msg</c></seecref></v>
+ </type>
<desc>
<p>Receives a message to the buffer in <c>x</c>.
<c>ei_xreceive_msg</c> allows the buffer in
@@ -797,18 +935,8 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
<c>ei_x_new</c>.</item>
</list>
<p>On success, the functions return <c>ERL_MSG</c> and the
- <c>msg</c> struct is initialized.
- <c>erlang_msg</c> is defined as follows:</p>
- <code type="none"><![CDATA[
-typedef struct {
- long msgtype;
- erlang_pid from;
- erlang_pid to;
- char toname[MAXATOMLEN+1];
- char cookie[MAXATOMLEN+1];
- erlang_trace token;
-} erlang_msg;
- ]]></code>
+ <seecref marker="#erlang_msg"><c>msg</c></seecref> struct
+ is initialized.</p>
<p><c>msgtype</c> identifies the type of message, and is
one of the following:</p>
<taglist>
@@ -846,6 +974,10 @@ typedef struct {
<name since=""><ret>int</ret><nametext>ei_receive_msg_tmo(int fd, erlang_msg* msg, ei_x_buff* x, unsigned imeout_ms)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_xreceive_msg_tmo(int fd, erlang_msg* msg, ei_x_buff* x, unsigned timeout_ms)</nametext></name>
<fsummary>Receive a message with optional time-out.</fsummary>
+ <type>
+ <v><seecref marker="ei#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ <v><seecref marker="#erlang_msg"><c>erlang_msg</c></seecref></v>
+ </type>
<desc>
<p>Equivalent to <c>ei_receive_msg</c> and <c>ei_xreceive_msg</c>
with an optional time-out argument,
@@ -866,6 +998,9 @@ typedef struct {
<func>
<name since=""><ret>int</ret><nametext>ei_reg_send(ei_cnode* ec, int fd, char* server_name, char* buf, int len)</nametext></name>
<fsummary>Send a message to a registered name.</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ </type>
<desc>
<p>Sends an Erlang term to a registered process.</p>
<list type="bulleted">
@@ -899,6 +1034,9 @@ if (ei_reg_send(&ec, fd, x.buff, x.index) < 0)
<name since=""><ret>int</ret><nametext>ei_reg_send_tmo(ei_cnode* ec, int fd, char* server_name, char* buf, int len, unsigned timeout_ms)</nametext></name>
<fsummary>Send a message to a registered name with optional time-out
</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ </type>
<desc>
<p>Equivalent to
<c>ei_reg_send</c> with an optional time-out argument,
@@ -911,6 +1049,11 @@ if (ei_reg_send(&ec, fd, x.buff, x.index) < 0)
<name since=""><ret>int</ret><nametext>ei_rpc_to(ei_cnode *ec, int fd, char *mod, char *fun, const char *argbuf, int argbuflen)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_rpc_from(ei_cnode *ec, int fd, int timeout, erlang_msg *msg, ei_x_buff *x)</nametext></name>
<fsummary>Remote Procedure Call from C to Erlang.</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ <v><seecref marker="ei#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ <v><seecref marker="#erlang_msg"><c>erlang_msg</c></seecref></v>
+ </type>
<desc>
<p>Supports calling Erlang functions on remote nodes.
<c>ei_rpc_to()</c> sends an RPC request to a remote node
@@ -1010,19 +1153,40 @@ if (ei_decode_version(result.buff, &index) < 0
<func>
<name since=""><ret>erlang_pid *</ret><nametext>ei_self(ei_cnode *ec)</nametext></name>
<fsummary>Retrieve the pid of the C-node.</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ <v><seecref marker="ei#erlang_pid"><c>erlang_pid</c></seecref></v>
+ </type>
<desc>
- <p>Retrieves the pid of the C-node. Every C-node
+ <p>Retrieves a generic pid of the C-node. Every C-node
has a (pseudo) pid used in <c>ei_send_reg</c>,
- <c>ei_rpc</c>,
+ <c>ei_rpc()</c>,
and others. This is contained in a field in the <c>ec</c>
- structure. It will be safe for a long time to fetch this
- field directly from the <c>ei_cnode</c> structure.</p>
+ structure. Do <em>not</em> modify this structure.
+ </p>
+ <p>
+ On success a pointer to the process identifier is returned.
+ On failure <c>NULL</c> is returned and <c>erl_errno</c> is
+ set.
+ </p>
+ <p>
+ The C-node identified by <c>ec</c> must have been initialized
+ and must have received a name prior to the call to <c>ei_self()</c>.
+ Initialization of the C-node is done by a call to
+ <seecref marker="#ei_connect_init"><c>ei_connect_init()</c></seecref>
+ or friends. If the name is dynamically assigned from the peer node, the
+ C-node also has to be connected.
+ </p>
+
</desc>
</func>
<func>
<name since=""><ret>int</ret><nametext>ei_send(int fd, erlang_pid* to, char* buf, int len)</nametext></name>
<fsummary>Send a message.</fsummary>
+ <type>
+ <v><seecref marker="ei#erlang_pid"><c>erlang_pid</c></seecref></v>
+ </type>
<desc>
<p>Sends an Erlang term to a process.</p>
<list type="bulleted">
@@ -1044,6 +1208,9 @@ if (ei_decode_version(result.buff, &index) < 0
<func>
<name since=""><ret>int</ret><nametext>ei_send_encoded(int fd, erlang_pid* to, char* buf, int len)</nametext></name>
<fsummary>Obsolete function to send a message.</fsummary>
+ <type>
+ <v><seecref marker="ei#erlang_pid"><c>erlang_pid</c></seecref></v>
+ </type>
<desc>
<p>Works exactly as <c>ei_send</c>, the alternative name is retained for
backward compatibility. The function will <em>not</em> be
@@ -1055,6 +1222,9 @@ if (ei_decode_version(result.buff, &index) < 0
<name since=""><ret>int</ret><nametext>ei_send_encoded_tmo(int fd, erlang_pid* to, char* buf, int len, unsigned timeout_ms)</nametext></name>
<fsummary>Obsolete function to send a message with optional time-out.
</fsummary>
+ <type>
+ <v><seecref marker="ei#erlang_pid"><c>erlang_pid</c></seecref></v>
+ </type>
<desc>
<p>Equivalent to
<c>ei_send_encoded</c> with an optional time-out argument,
@@ -1066,6 +1236,9 @@ if (ei_decode_version(result.buff, &index) < 0
<name since=""><ret>int</ret><nametext>ei_send_reg_encoded(int fd, const erlang_pid *from, const char *to, const char *buf, int len)</nametext></name>
<fsummary>Obsolete function to send a message to a registered name.
</fsummary>
+ <type>
+ <v><seecref marker="ei#erlang_pid"><c>erlang_pid</c></seecref></v>
+ </type>
<desc>
<p>This function is retained for compatibility with code
generated by the interface compiler and with code following
@@ -1076,17 +1249,8 @@ if (ei_decode_version(result.buff, &index) < 0
<c>erlang_pid</c>,
which is to be the process identifier of the sending process
(in the Erlang distribution protocol).</p>
- <p>A suitable <c>erlang_pid</c> can be constructed from the
- <c>ei_cnode</c> structure by the following example
- code:</p>
- <code type="none"><![CDATA[
-ei_cnode ec;
-erlang_pid *self;
-int fd; /* the connection fd */
-...
-self = ei_self(&ec);
-self->num = fd;
- ]]></code>
+ <p>A suitable <c>erlang_pid</c> can be retrieved from the
+ <c>ei_cnode</c> structure by calling <c>ei_self(cnode_pointer)</c>.</p>
</desc>
</func>
@@ -1094,6 +1258,9 @@ self->num = fd;
<name since=""><ret>int</ret><nametext>ei_send_reg_encoded_tmo(int fd, const erlang_pid *from, const char *to, const char *buf, int len)</nametext></name>
<fsummary>Obsolete function to send a message to a registered name with
time-out.</fsummary>
+ <type>
+ <v><seecref marker="ei#erlang_pid"><c>erlang_pid</c></seecref></v>
+ </type>
<desc>
<p>Equivalent to
<c>ei_send_reg_encoded</c> with an optional time-out argument,
@@ -1104,6 +1271,9 @@ self->num = fd;
<func>
<name since=""><ret>int</ret><nametext>ei_send_tmo(int fd, erlang_pid* to, char* buf, int len, unsigned timeout_ms)</nametext></name>
<fsummary>Send a message with optional time-out.</fsummary>
+ <type>
+ <v><seecref marker="ei#erlang_pid"><c>erlang_pid</c></seecref></v>
+ </type>
<desc>
<p>Equivalent to
<c>ei_send</c> with an optional time-out argument,
@@ -1116,6 +1286,9 @@ self->num = fd;
<name since=""><ret>const char *</ret><nametext>ei_thishostname(ei_cnode *ec)</nametext></name>
<name since=""><ret>const char *</ret><nametext>ei_thisalivename(ei_cnode *ec)</nametext></name>
<fsummary>Retrieve some values.</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ </type>
<desc>
<p>Can be used to retrieve information about
the C-node. These values are initially set with
@@ -1131,6 +1304,9 @@ self->num = fd;
<func>
<name since=""><ret>int</ret><nametext>ei_unpublish(ei_cnode *ec)</nametext></name>
<fsummary>Forcefully unpublish a node name.</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ </type>
<desc>
<p>Can be called by a process to unregister a
specified node from EPMD on the local host. This is, however, usually
@@ -1154,6 +1330,9 @@ self->num = fd;
<func>
<name since=""><ret>int</ret><nametext>ei_unpublish_tmo(ei_cnode *ec, unsigned timeout_ms)</nametext></name>
<fsummary>Unpublish a node name with optional time-out.</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ </type>
<desc>
<p>Equivalent to
<c>ei_unpublish</c> with an optional time-out argument,
diff --git a/lib/erl_interface/doc/src/ei_global.xml b/lib/erl_interface/doc/src/ei_global.xml
index ecca1aad68..148068490e 100644
--- a/lib/erl_interface/doc/src/ei_global.xml
+++ b/lib/erl_interface/doc/src/ei_global.xml
@@ -35,9 +35,6 @@
<lib>ei_global</lib>
<libsummary>Access globally registered names.</libsummary>
<description>
- <note><p>The support for VxWorks is deprecated as of OTP 22, and
- will be removed in OTP 23.</p></note>
-
<p>This module provides support for registering, looking
up, and unregistering names in the <c>global</c> module.
For more information, see
@@ -54,14 +51,14 @@
<name since=""><ret>char **</ret><nametext>ei_global_names(ec,fd,count)</nametext></name>
<fsummary>Obtain list of global names.</fsummary>
<type>
- <v>ei_cnode *ec;</v>
+ <v><seecref marker="ei_connect#ei_cnode"><c>ei_cnode</c></seecref> *ec;</v>
<v>int fd;</v>
<v>int *count;</v>
</type>
<desc>
<p>Retrieves a list of all known global names.</p>
<list type="bulleted">
- <item><c>ec</c> is the ei_cnode representing the current cnode.</item>
+ <item><c>ec</c> is the <c>ei_cnode</c> representing the current cnode.</item>
<item><c>fd</c> is an open descriptor to an Erlang
connection.</item>
<item><c>count</c> is the address of an integer, or
@@ -89,7 +86,7 @@
<type>
<v>int fd;</v>
<v>const char *name;</v>
- <v>erlang_pid *pid;</v>
+ <v><seecref marker="ei#erlang_pid"><c>erlang_pid</c></seecref> *pid;</v>
</type>
<desc>
<p>Registers a name in <c>global</c>.</p>
@@ -111,14 +108,14 @@
<name since=""><ret>int</ret><nametext>ei_global_unregister(ec,fd,name)</nametext></name>
<fsummary>Unregister a name from global.</fsummary>
<type>
- <v>ei_cnode *ec;</v>
+ <v><seecref marker="ei_connect#ei_cnode"><c>ei_cnode</c></seecref> *ec;</v>
<v>int fd;</v>
<v>const char *name;</v>
</type>
<desc>
<p>Unregisters a name from <c>global</c>.</p>
<list type="bulleted">
- <item><c>ec</c> is the ei_cnode representing the current cnode.</item>
+ <item><c>ec</c> is the <c>ei_cnode</c> representing the current cnode.</item>
<item><c>fd</c> is an open descriptor to an Erlang
connection.</item>
<item><c>name</c> is the name to unregister from
@@ -132,16 +129,16 @@
<name since=""><ret>int</ret><nametext>ei_global_whereis(ec,fd,name,pid,node)</nametext></name>
<fsummary>Look up a name in global.</fsummary>
<type>
- <v>ei_cnode *ec;</v>
+ <v><seecref marker="ei_connect#ei_cnode"><c>ei_cnode</c></seecref> *ec;</v>
<v>int fd;</v>
<v>const char *name;</v>
- <v>erlang_pid* pid;</v>
+ <v><seecref marker="ei#erlang_pid"><c>erlang_pid</c></seecref> *pid;</v>
<v>char *node;</v>
</type>
<desc>
<p>Looks up a name in <c>global</c>.</p>
<list type="bulleted">
- <item><c>ec</c> is the ei_cnode representing the current cnode.</item>
+ <item><c>ec</c> is the <c>ei_cnode</c> representing the current cnode.</item>
<item><c>fd</c> is an open descriptor to an Erlang
connection.</item>
<item><c>name</c> is the name that is to be looked up in
diff --git a/lib/erl_interface/doc/src/ei_users_guide.xml b/lib/erl_interface/doc/src/ei_users_guide.xml
index 25952c2025..5dfaf556da 100644
--- a/lib/erl_interface/doc/src/ei_users_guide.xml
+++ b/lib/erl_interface/doc/src/ei_users_guide.xml
@@ -34,12 +34,6 @@
</header>
<section>
- <title>Deprecation and Removal</title>
- <note><p>The support for VxWorks is deprecated as of OTP 22, and
- will be removed in OTP 23.</p></note>
- </section>
-
- <section>
<title>Introduction</title>
<p>The <c>Erl_Interface</c> library contains functions that help you
integrate programs written in C and Erlang. The functions in
@@ -536,6 +530,12 @@ ei_global_unregister(ec,fd,servicename); ]]></code>
<section>
<title>Using the Registry</title>
+
+ <note><p>This functionality is deprecated as of OTP 23, and will be
+ removed in OTP 24. Reasonably new <c>gcc</c> compilers will issue
+ deprecation warnings. In order to disable these warnings, define the
+ macro <c>EI_NO_DEPR_WARN</c>.</p></note>
+
<p>This section describes the use of the registry, a simple mechanism
for storing key-value pairs in a C-node, as well as backing them up or
restoring them from an <c>Mnesia</c> table on an Erlang node. For more
diff --git a/lib/erl_interface/doc/src/erl_call_cmd.xml b/lib/erl_interface/doc/src/erl_call_cmd.xml
index 4a77aa954e..04b5ec74bf 100644
--- a/lib/erl_interface/doc/src/erl_call_cmd.xml
+++ b/lib/erl_interface/doc/src/erl_call_cmd.xml
@@ -183,6 +183,13 @@
specified, an Erlang node is started (if necessary) with
<c>erl -sname</c>.</p>
</item>
+ <tag><c>-timeout Seconds</c></tag>
+ <item>
+ <p>(<em>Optional.</em>) Aborts the <c>erl_call</c> process after
+ the timeout expires. Note that this does not abort commands that
+ have already been started with <c>-a</c>, <c>-e</c>, or similar.
+ </p>
+ </item>
<tag><c>-v</c></tag>
<item>
<p>(<em>Optional.</em>) Prints a lot of <c>verbose</c>
diff --git a/lib/erl_interface/doc/src/notes.xml b/lib/erl_interface/doc/src/notes.xml
index ba5f501e85..37eb379d95 100644
--- a/lib/erl_interface/doc/src/notes.xml
+++ b/lib/erl_interface/doc/src/notes.xml
@@ -31,6 +31,178 @@
</header>
<p>This document describes the changes made to the Erl_interface application.</p>
+<section><title>Erl_Interface 4.0</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix various compiler warnings on 64-bit Windows.</p>
+ <p>
+ Own Id: OTP-15800</p>
+ </item>
+ <item>
+ <p><c>erl_call</c> will now work properly on systems that
+ cannot resolve their own hostname.</p>
+ <p>
+ Own Id: OTP-16604</p>
+ </item>
+ <item>
+ <p>Various bug fixes:</p> <list> <item>Internal error
+ checking in various functions.</item> <item><seecref
+ marker="ei_connect#ei_rpc"><c>ei_rpc()</c></seecref>
+ accepted any 2-tuple message as an rpc response.</item>
+ <item><seecref
+ marker="ei#ei_decode_ref"><c>ei_decode_ref()</c></seecref>
+ now refuse to write outside of allocated memory in case a
+ huge reference is decoded.</item> <item><seecref
+ marker="ei#ei_decode_ei_term"><c>ei_decode_ei_term()</c></seecref>
+ now reports the same term types as <seecref
+ marker="ei#ei_get_type"><c>ei_get_type()</c></seecref>.</item>
+ </list>
+ <p>
+ Own Id: OTP-16623</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>A client node can receive its node name dynamically
+ from the node that it first connects to. This featured
+ can by used by</p> <list> <item><p>starting with <c>erl
+ -sname undefined</c></p></item> <item><p>erl_interface
+ functions <c>ei_connect_init</c> and friends</p></item>
+ <item><p><c>erl_call -R</c></p></item> </list>
+ <p>
+ Own Id: OTP-13812</p>
+ </item>
+ <item>
+ <p>
+ Increased size of node incarnation numbers (aka
+ "creation"), from 2 bits to 32 bits. This will reduce the
+ risk of pids/ports/refs, from different node incarnation
+ with the same name, being mixed up.</p>
+ <p>
+ Own Id: OTP-15603</p>
+ </item>
+ <item>
+ <p>
+ Fix various build issues when compiling Erlang/OTP to the
+ IBM AIX platform.</p>
+ <p>
+ Own Id: OTP-15866 Aux Id: PR-2110 </p>
+ </item>
+ <item>
+ <p>
+ Improved node connection setup handshake protocol. Made
+ possible to agree on protocol version without dependence
+ on <c>epmd</c> or other prior knowledge of peer node
+ version. Also added exchange of node incarnation
+ ("creation") values and expanded the distribution
+ capability flag field from 32 to 64 bits.</p>
+ <p>
+ Own Id: OTP-16229</p>
+ </item>
+ <item>
+ <p>
+ New <c>erl_call</c> option <c>-address [Host]:Port</c> to
+ connect directly to a node without being dependent on
+ <c>epmd</c> to resolve the node name.</p>
+ <p>
+ Own Id: OTP-16251</p>
+ </item>
+ <item>
+ <p>
+ As announced in OTP 22.0, the deprecated parts of
+ <c>erl_interface</c> have now been removed (essentially
+ all C functions with prefix <c>erl_</c>).</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16328</p>
+ </item>
+ <item>
+ <p>
+ As announced in OTP 22.0, the previously existing limited
+ support for VxWorks has now been removed.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16329 Aux Id: OTP-15621 </p>
+ </item>
+ <item>
+ <p>
+ New function <c>ei_connect_host_port</c> and friends to
+ allow node connection without being dependent on
+ <c>epmd</c> for node name resolution.</p>
+ <p>
+ Own Id: OTP-16496 Aux Id: OTP-16251 </p>
+ </item>
+ <item>
+ <p>A number of new functions have been added to the
+ <c>erl_interface</c> API:</p> <list> <item><seecref
+ marker="erl_interface:ei#ei_cmp_pids"><c>ei_cmp_pids()</c></seecref></item>
+ <item><seecref
+ marker="erl_interface:ei#ei_cmp_ports"><c>ei_cmp_ports()</c></seecref></item>
+ <item><seecref
+ marker="erl_interface:ei#ei_cmp_refs"><c>ei_cmp_refs()</c></seecref></item>
+ <item><seecref
+ marker="erl_interface:ei#ei_decode_iodata"><c>ei_decode_iodata()</c></seecref></item>
+ <item><seecref
+ marker="erl_interface:ei_connect#ei_make_pid"><c>ei_make_pid()</c></seecref></item>
+ <item><seecref
+ marker="erl_interface:ei_connect#ei_make_ref"><c>ei_make_ref()</c></seecref></item>
+ </list>
+ <p>
+ Own Id: OTP-16594</p>
+ </item>
+ <item>
+ <p>Added a <c>-timeout</c> option to <c>erl_call</c>.</p>
+ <p>
+ Own Id: OTP-16624</p>
+ </item>
+ <item>
+ <p>The <c>erl_interface</c> <seecref
+ marker="erl_interface:registry"><c>registry</c></seecref>
+ functionality is deprecated as of OTP 23, and will be
+ removed in OTP 24. Reasonably new <c>gcc</c> compilers
+ will issue deprecation warnings when using this
+ functionality. In order to disable these warnings, define
+ the macro <c>EI_NO_DEPR_WARN</c>.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16630</p>
+ </item>
+ <item>
+ <p>
+ Documentation improvements.</p>
+ <p>
+ Own Id: OTP-16633</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 3.13.2</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/erl_interface/doc/src/ref_man.xml b/lib/erl_interface/doc/src/ref_man.xml
index 4900c0f5f3..064a83a4ba 100644
--- a/lib/erl_interface/doc/src/ref_man.xml
+++ b/lib/erl_interface/doc/src/ref_man.xml
@@ -29,8 +29,6 @@
<file>ref_man.xml</file>
</header>
<description>
- <note><p>The support for VxWorks is deprecated as of OTP 22, and
- will be removed in OTP 23.</p></note>
</description>
<xi:include href="ei.xml"/>
<xi:include href="ei_connect.xml"/>
diff --git a/lib/erl_interface/doc/src/registry.xml b/lib/erl_interface/doc/src/registry.xml
index 28c79ad0fe..92858516d9 100644
--- a/lib/erl_interface/doc/src/registry.xml
+++ b/lib/erl_interface/doc/src/registry.xml
@@ -35,6 +35,11 @@
<lib>registry</lib>
<libsummary>Store and back up key-value pairs.</libsummary>
<description>
+ <note><p>This functionality is deprecated as of OTP 23, and will be
+ removed in OTP 24. Reasonably new <c>gcc</c> compilers will issue
+ deprecation warnings. In order to disable these warnings, define the
+ macro <c>EI_NO_DEPR_WARN</c>.</p></note>
+
<p>This module provides support for storing key-value
pairs in a table known as a registry, backing up registries to
<seeerl marker="mnesia:mnesia">Mnesia</seeerl>
diff --git a/lib/erl_interface/include/ei.h b/lib/erl_interface/include/ei.h
index 6b75a213d0..803e22d4b2 100644
--- a/lib/erl_interface/include/ei.h
+++ b/lib/erl_interface/include/ei.h
@@ -366,6 +366,7 @@ typedef struct ei_cnode_s {
erlang_pid self;
ei_socket_callbacks *cbs;
void *setup_context;
+ unsigned int pidsn;
} ei_cnode;
typedef struct in_addr *Erl_IpAddr;
@@ -446,6 +447,8 @@ const char *ei_thishostname(const ei_cnode* ec);
const char *ei_thisalivename(const ei_cnode* ec);
erlang_pid *ei_self(ei_cnode* ec);
+int ei_make_pid(ei_cnode *ec, erlang_pid *pid);
+int ei_make_ref(ei_cnode *ec, erlang_ref *ref);
/*
* settings
@@ -568,6 +571,7 @@ int ei_decode_trace(const char *buf, int *index, erlang_trace *p);
int ei_decode_tuple_header(const char *buf, int *index, int *arity);
int ei_decode_list_header(const char *buf, int *index, int *arity);
int ei_decode_map_header(const char *buf, int *index, int *arity);
+int ei_decode_iodata(const char *buf, int* index, int *szp, char *out_buf);
/*
* ei_decode_ei_term() returns 1 if term is decoded, 0 if term is OK,
@@ -599,6 +603,10 @@ int ei_x_append(ei_x_buff* x, const ei_x_buff* x2);
int ei_x_append_buf(ei_x_buff* x, const char* buf, int len);
int ei_skip_term(const char* buf, int* index);
+int ei_cmp_refs(erlang_ref *a, erlang_ref *b);
+int ei_cmp_pids(erlang_pid *a, erlang_pid *b);
+int ei_cmp_ports(erlang_port *a, erlang_port *b);
+
/***************************************************************************
*
* Hash types needed by registry types
@@ -691,9 +699,9 @@ int ei_init(void);
* be specified in all subsequent calls to registry functions. You can
* open as many registries as you like.
*/
-ei_reg *ei_reg_open(int size);
-int ei_reg_resize(ei_reg *oldreg, int newsize);
-int ei_reg_close(ei_reg *reg);
+ei_reg *ei_reg_open(int size) EI_DEPRECATED_ATTR;
+int ei_reg_resize(ei_reg *oldreg, int newsize) EI_DEPRECATED_ATTR;
+int ei_reg_close(ei_reg *reg) EI_DEPRECATED_ATTR;
/* set values... these routines assign values to keys. If the key
* exists, the previous value is discarded and the new one replaces
@@ -710,10 +718,10 @@ int ei_reg_close(ei_reg *reg);
* On success the function returns 0, otherwise a value
* indicating the reason for failure will be returned.
*/
-int ei_reg_setival(ei_reg *reg, const char *key, long i);
-int ei_reg_setfval(ei_reg *reg, const char *key, double f);
-int ei_reg_setsval(ei_reg *reg, const char *key, const char *s);
-int ei_reg_setpval(ei_reg *reg, const char *key, const void *p, int size);
+int ei_reg_setival(ei_reg *reg, const char *key, long i) EI_DEPRECATED_ATTR;
+int ei_reg_setfval(ei_reg *reg, const char *key, double f) EI_DEPRECATED_ATTR;
+int ei_reg_setsval(ei_reg *reg, const char *key, const char *s) EI_DEPRECATED_ATTR;
+int ei_reg_setpval(ei_reg *reg, const char *key, const void *p, int size) EI_DEPRECATED_ATTR;
/* general set function (specifiy type via flags)
* optional arguments are as for equivalent type-specific function,
@@ -723,16 +731,16 @@ int ei_reg_setpval(ei_reg *reg, const char *key, const void *p, int size);
* ei_reg_setval(fd, path, EI_STR, const char *s);
* ei_reg_setval(fd, path, EI_BIN, const void *p, int size);
*/
-int ei_reg_setval(ei_reg *reg, const char *key, int flags, ...);
+int ei_reg_setval(ei_reg *reg, const char *key, int flags, ...) EI_DEPRECATED_ATTR;
/* get value of specific type object */
/* warning: it may be difficult to detect errors when using these
* functions, since the error values are returned "in band"
*/
-long ei_reg_getival(ei_reg *reg, const char *key);
-double ei_reg_getfval(ei_reg *reg, const char *key);
-const char *ei_reg_getsval(ei_reg *reg, const char *key);
-const void *ei_reg_getpval(ei_reg *reg, const char *key, int *size);
+long ei_reg_getival(ei_reg *reg, const char *key) EI_DEPRECATED_ATTR;
+double ei_reg_getfval(ei_reg *reg, const char *key) EI_DEPRECATED_ATTR;
+const char *ei_reg_getsval(ei_reg *reg, const char *key) EI_DEPRECATED_ATTR;
+const void *ei_reg_getpval(ei_reg *reg, const char *key, int *size) EI_DEPRECATED_ATTR;
/* get value of any type object (must specify)
* Retrieve a value from an object. The type of value expected and a
@@ -748,7 +756,7 @@ const void *ei_reg_getpval(ei_reg *reg, const char *key, int *size);
* for BIN objects an int* is needed to return the size of the object, i.e.
* int ei_reg_getval(ei_reg *reg, const char *path, int flags, void **p, int *size);
*/
-int ei_reg_getval(ei_reg *reg, const char *key, int flags, ...);
+int ei_reg_getval(ei_reg *reg, const char *key, int flags, ...) EI_DEPRECATED_ATTR;
/* mark the object as dirty. Normally this operation will not be
* necessary, as it is done automatically by all of the above 'set'
@@ -758,26 +766,26 @@ int ei_reg_getval(ei_reg *reg, const char *key, int flags, ...);
* backup operation. Use this function to set the dirty bit on the
* object.
*/
-int ei_reg_markdirty(ei_reg *reg, const char *key);
+int ei_reg_markdirty(ei_reg *reg, const char *key) EI_DEPRECATED_ATTR;
/* remove objects. The value, if any, is discarded. For STR and BIN
* objects, the object itself is removed using free(). */
-int ei_reg_delete(ei_reg *reg, const char *key);
+int ei_reg_delete(ei_reg *reg, const char *key) EI_DEPRECATED_ATTR;
/* get information about an object */
-int ei_reg_stat(ei_reg *reg, const char *key, struct ei_reg_stat *obuf);
+int ei_reg_stat(ei_reg *reg, const char *key, struct ei_reg_stat *obuf) EI_DEPRECATED_ATTR;
/* get information about table */
-int ei_reg_tabstat(ei_reg *reg, struct ei_reg_tabstat *obuf);
+int ei_reg_tabstat(ei_reg *reg, struct ei_reg_tabstat *obuf) EI_DEPRECATED_ATTR;
/* dump to / restore from backup */
/* fd is open descriptor to Erlang, mntab is Mnesia table name */
/* flags here: */
#define EI_FORCE 0x1 /* dump all records (not just dirty ones) */
#define EI_NOPURGE 0x2 /* don't purge deleted records */
-int ei_reg_dump(int fd, ei_reg *reg, const char *mntab, int flags);
-int ei_reg_restore(int fd, ei_reg *reg, const char *mntab);
-int ei_reg_purge(ei_reg *reg);
+int ei_reg_dump(int fd, ei_reg *reg, const char *mntab, int flags) EI_DEPRECATED_ATTR;
+int ei_reg_restore(int fd, ei_reg *reg, const char *mntab) EI_DEPRECATED_ATTR;
+int ei_reg_purge(ei_reg *reg) EI_DEPRECATED_ATTR;
/* -------------------------------------------------------------------- */
/* The ei_global functions */
diff --git a/lib/erl_interface/src/Makefile.in b/lib/erl_interface/src/Makefile.in
index 55827ce097..7ff3f09abb 100644
--- a/lib/erl_interface/src/Makefile.in
+++ b/lib/erl_interface/src/Makefile.in
@@ -331,6 +331,7 @@ DECODESRC = \
decode/decode_double.c \
decode/decode_fun.c \
decode/decode_intlist.c \
+ decode/decode_iodata.c \
decode/decode_list_header.c \
decode/decode_long.c \
decode/decode_pid.c \
@@ -393,7 +394,8 @@ MISCSRC = \
misc/get_type.c \
misc/show_msg.c \
misc/ei_compat.c \
- misc/ei_init.c
+ misc/ei_init.c \
+ misc/ei_cmp_nc.c
REGISTRYSRC = \
registry/hash_dohash.c \
@@ -744,9 +746,7 @@ release: opt
$(INSTALL_DATA) $(HEADERS) "$(RELEASE_PATH)/usr/include"
$(INSTALL_DATA) $(OBJ_TARGETS) "$(RELSYSDIR)/lib"
$(INSTALL_DATA) $(OBJ_TARGETS) "$(RELEASE_PATH)/usr/lib"
-ifneq ($(EXE_TARGETS),)
$(INSTALL_PROGRAM) $(EXE_TARGETS) "$(RELSYSDIR)/bin"
-endif
$(INSTALL_DATA) $(EXTRA) "$(RELSYSDIR)/src"
$(INSTALL_DATA) connect/*.[ch] "$(RELSYSDIR)/src/connect"
$(INSTALL_DATA) decode/*.[ch] "$(RELSYSDIR)/src/decode"
diff --git a/lib/erl_interface/src/connect/ei_connect.c b/lib/erl_interface/src/connect/ei_connect.c
index 40efdd101a..a5db73799d 100644
--- a/lib/erl_interface/src/connect/ei_connect.c
+++ b/lib/erl_interface/src/connect/ei_connect.c
@@ -525,11 +525,272 @@ const char *ei_thiscookie(const ei_cnode* ec)
return (const char *)ec->ei_connect_cookie;
}
+static int
+check_initialized_node(ei_cnode *ec)
+{
+ /*
+ * Try to guard against returning garbage pids and refs
+ * by verifying that the node has got its name...
+ */
+ int i, at, end;
+ char *nodename = &ec->thisnodename[0];
+
+ for (i = at = end = 0; i < sizeof(ec->thisnodename); i++) {
+ if (!nodename[i]) {
+ end = !0;
+ break;
+ }
+ if (nodename[i] == '@')
+ at = !0;
+ }
+
+ if (!at || !end) {
+ erl_errno = EINVAL;
+ return ERL_ERROR;
+ }
+
+ return 0;
+}
+
erlang_pid *ei_self(ei_cnode* ec)
{
+ int err = check_initialized_node(ec);
+ if (err)
+ return NULL;
return &ec->self;
}
+/*
+ * ei_make_pid()
+ */
+
+#undef EI_MAKE_PID_ATOMIC__
+#ifdef _REENTRANT
+# if (SIZEOF_INT == 4 \
+ && (ETHR_HAVE___atomic_compare_exchange_n & 4) \
+ && (ETHR_HAVE___atomic_load_n & 4))
+# define EI_MAKE_PID_ATOMIC__
+# else /* !EI_MAKE_PID_ATOMIC__ */
+static ei_mutex_t *pid_mtx = NULL;
+# endif /* !EI_MAKE_PID_ATOMIC__ */
+#endif /* _REENTRANT */
+
+static int
+init_make_pid(int late)
+{
+#if defined(_REENTRANT) && !defined(EI_MAKE_PID_ATOMIC__)
+
+ if (late)
+ return ENOTSUP; /* Refuse doing unsafe initialization... */
+
+ pid_mtx = ei_mutex_create();
+ if (!pid_mtx)
+ return ENOMEM;
+
+#endif /* _REENTRANT */
+
+ return 0;
+}
+
+int ei_make_pid(ei_cnode *ec, erlang_pid *pid)
+{
+ unsigned int new;
+ int err;
+
+ if (!ei_connect_initialized) {
+ fprintf(stderr,"<ERROR> erl_interface not initialized\n");
+ exit(1);
+ }
+
+ err = check_initialized_node(ec);
+ if (err) {
+ /*
+ * write invalid utf8 in nodename which will make
+ * ei_encode_pid() fail if used...
+ */
+ pid->node[0] = 0xff;
+ pid->node[1] = 0;
+ pid->serial = -1;
+ pid->num = -1;
+ return err;
+ }
+
+ strcpy(pid->node, ec->thisnodename);
+ pid->creation = ec->creation;
+
+ /*
+ * We avoid creating pids with serial set to 0 since the
+ * documentation previously gave some really bad advise
+ * of modifying the 'num' field in the pid returned by
+ * ei_self(). Since 'serial' field in pid returned by
+ * ei_self() is initialized to 0, pids created by
+ * ei_make_pid() wont clash with such badly created pids
+ * using ei_self() unless user also modified serial, but
+ * that has at least never been suggested by the
+ * documentation.
+ */
+
+#ifdef EI_MAKE_PID_ATOMIC__
+ {
+ unsigned int xchg = __atomic_load_n(&ec->pidsn, __ATOMIC_RELAXED);
+ do {
+ new = xchg + 1;
+ if ((new & 0x0fff8000) == 0)
+ new = 0x8000; /* serial==0 -> serial=1 num=0 */
+ } while(!__atomic_compare_exchange_n(&ec->pidsn, &xchg, new, 0,
+ __ATOMIC_ACQ_REL,
+ __ATOMIC_RELAXED));
+ }
+#else /* !EI_MAKE_PID_ATOMIC__ */
+
+#ifdef _REENTRANT
+ ei_mutex_lock(pid_mtx, 0);
+#endif
+
+ new = ec->pidsn + 1;
+ if ((new & 0x0fff8000) == 0)
+ new = 0x8000; /* serial==0 -> serial=1 num=0 */
+
+ ec->pidsn = new;
+
+#ifdef _REENTRANT
+ ei_mutex_unlock(pid_mtx);
+#endif
+
+#endif /* !EI_MAKE_PID_ATOMIC__ */
+
+ pid->num = new & 0x7fff; /* 15-bits */
+ pid->serial = (new >> 15) & 0x1fff; /* 13-bits */
+
+ return 0;
+}
+
+/*
+ * ei_make_ref()
+ */
+
+#undef EI_MAKE_REF_ATOMIC__
+#ifdef _REENTRANT
+# if ((SIZEOF_LONG == 8 || SIZEOF_LONGLONG == 8) \
+ && (ETHR_HAVE___atomic_compare_exchange_n & 8) \
+ && (ETHR_HAVE___atomic_load_n & 8))
+# define EI_MAKE_REF_ATOMIC__
+# if SIZEOF_LONG == 8
+typedef unsigned long ei_atomic_ref__;
+# else
+typedef unsigned long long ei_atomic_ref__;
+# endif
+# else /* !EI_MAKE_REF_ATOMIC__ */
+static ei_mutex_t *ref_mtx = NULL;
+# endif /* !EI_MAKE_REF_ATOMIC__ */
+#endif /* _REENTRANT */
+
+/*
+ * We use a global counter for all c-nodes in this process.
+ * We wont wrap anyway due to the enormous amount of values
+ * available.
+ */
+#ifdef EI_MAKE_REF_ATOMIC__
+static ei_atomic_ref__ ref_count;
+#else
+static unsigned int ref_count[3];
+#endif
+
+static int
+init_make_ref(int late)
+{
+
+#ifdef EI_MAKE_REF_ATOMIC__
+ ref_count = 0;
+#else /* !EI_MAKE_REF_ATOMIC__ */
+
+#ifdef _REENTRANT
+
+ if (late)
+ return ENOTSUP; /* Refuse doing unsafe initialization... */
+
+ ref_mtx = ei_mutex_create();
+ if (!ref_mtx)
+ return ENOMEM;
+
+#endif /* _REENTRANT */
+
+ ref_count[0] = 0;
+ ref_count[1] = 0;
+ ref_count[2] = 0;
+
+#endif /* !EI_MAKE_REF_ATOMIC__ */
+
+ return 0;
+}
+
+int ei_make_ref(ei_cnode *ec, erlang_ref *ref)
+{
+ int err;
+ if (!ei_connect_initialized) {
+ fprintf(stderr,"<ERROR> erl_interface not initialized\n");
+ exit(1);
+ }
+
+ err = check_initialized_node(ec);
+ if (err) {
+ /*
+ * write invalid utf8 in nodename which will make
+ * ei_encode_ref() fail if used...
+ */
+ ref->node[0] = 0xff;
+ ref->node[1] = 0;
+ ref->len = -1;
+ return err;
+ }
+
+ strcpy(ref->node, ec->thisnodename);
+ ref->creation = ec->creation;
+ ref->len = 3;
+
+#ifdef EI_MAKE_REF_ATOMIC__
+ {
+ ei_atomic_ref__ xchg, new;
+ xchg = __atomic_load_n(&ref_count, __ATOMIC_RELAXED);
+ do {
+ new = xchg + 1;
+ } while(!__atomic_compare_exchange_n(&ref_count, &xchg, new, 0,
+ __ATOMIC_ACQ_REL,
+ __ATOMIC_RELAXED));
+ ref->n[0] = (unsigned int) (new & 0x3ffff);
+ ref->n[1] = (unsigned int) ((new >> 18) & 0xffffffff);
+ ref->n[2] = (unsigned int) ((new >> (18+32)) & 0xffffffff);
+ }
+#else /* !EI_MAKE_REF_ATOMIC__ */
+
+#ifdef _REENTRANT
+ ei_mutex_lock(ref_mtx, 0);
+#endif
+
+ ref->n[0] = ref_count[0];
+ ref->n[1] = ref_count[1];
+ ref->n[2] = ref_count[2];
+
+ ref_count[0]++;
+ ref_count[0] &= 0x3ffff;
+ if (ref_count[0] == 0) {
+ ref_count[1]++;
+ ref_count[1] &= 0xffffffff;
+ if (ref_count[1] == 0) {
+ ref_count[2]++;
+ ref_count[2] &= 0xffffffff;
+ }
+ }
+
+#ifdef _REENTRANT
+ ei_mutex_unlock(ref_mtx);
+#endif
+
+#endif /* !EI_MAKE_REF_ATOMIC__ */
+
+ return 0;
+}
+
/* two internal functions that will let us support different cookies
* (to be able to connect to other nodes that don't have the same
* cookie as each other or us)
@@ -616,6 +877,18 @@ static int init_connect(int late)
return error;
}
+ error = init_make_ref(late);
+ if (error) {
+ EI_TRACE_ERR0("ei_init_connect","can't initiate ei_make_ref()");
+ return error;
+ }
+
+ error = init_make_pid(late);
+ if (error) {
+ EI_TRACE_ERR0("ei_init_connect","can't initiate ei_make_pid()");
+ return error;
+ }
+
ei_connect_initialized = !0;
return 0;
}
@@ -650,6 +923,7 @@ int ei_connect_xinit_ussi(ei_cnode* ec, const char *thishostname,
}
ec->creation = creation;
+ ec->pidsn = 0;
if (cookie) {
if (strlen(cookie) >= sizeof(ec->ei_connect_cookie)) {
@@ -1475,11 +1749,8 @@ int ei_receive(int fd, unsigned char *bufp, int bufsize)
int ei_reg_send_tmo(ei_cnode* ec, int fd, char *server_name,
char* buf, int len, unsigned ms)
{
- erlang_pid *self = ei_self(ec);
- self->num = fd;
-
/* erl_errno and return code is set by ei_reg_send_encoded_tmo() */
- return ei_send_reg_encoded_tmo(fd, self, server_name, buf, len, ms);
+ return ei_send_reg_encoded_tmo(fd, ei_self(ec), server_name, buf, len, ms);
}
@@ -1588,27 +1859,45 @@ int ei_rpc_to(ei_cnode *ec, int fd, char *mod, char *fun,
ei_x_buff x;
erlang_pid *self = ei_self(ec);
- self->num = fd;
+ int err = ERL_ERROR;
/* encode header */
- ei_x_new_with_version(&x);
- ei_x_encode_tuple_header(&x, 2); /* A */
-
- self->num = fd;
- ei_x_encode_pid(&x, self); /* A 1 */
-
- ei_x_encode_tuple_header(&x, 5); /* B A 2 */
- ei_x_encode_atom(&x, "call"); /* B 1 */
- ei_x_encode_atom(&x, mod); /* B 2 */
- ei_x_encode_atom(&x, fun); /* B 3 */
- ei_x_append_buf(&x, buf, len); /* B 4 */
- ei_x_encode_atom(&x, "user"); /* B 5 */
-
- /* ei_x_encode_atom(&x,"user"); */
- ei_send_reg_encoded(fd, self, "rex", x.buff, x.index);
- ei_x_free(&x);
-
+ if (ei_x_new_with_version(&x) < 0)
+ goto einval;
+ if (ei_x_encode_tuple_header(&x, 2) < 0) /* A */
+ goto einval;
+
+ if (ei_x_encode_pid(&x, self) < 0) /* A 1 */
+ goto einval;
+
+ if (ei_x_encode_tuple_header(&x, 5) < 0) /* B A 2 */
+ goto einval;
+ if (ei_x_encode_atom(&x, "call") < 0) /* B 1 */
+ goto einval;
+ if (ei_x_encode_atom(&x, mod) < 0) /* B 2 */
+ goto einval;
+ if (ei_x_encode_atom(&x, fun) < 0) /* B 3 */
+ goto einval;
+ if (ei_x_append_buf(&x, buf, len) < 0) /* B 4 */
+ goto einval;
+ if (ei_x_encode_atom(&x, "user") < 0) /* B 5 */
+ goto einval;
+
+ err = ei_send_reg_encoded(fd, self, "rex", x.buff, x.index);
+ if (err)
+ goto error;
+
+ ei_x_free(&x);
+
return 0;
+
+einval:
+ EI_CONN_SAVE_ERRNO__(EINVAL);
+
+error:
+ if (x.buff != NULL)
+ ei_x_free(&x);
+ return err;
} /* rpc_to */
/*
@@ -1626,11 +1915,6 @@ int ei_rpc_from(ei_cnode *ec, int fd, int timeout, erlang_msg *msg,
return res;
} /* rpc_from */
- /*
- * A true RPC. It return a NULL pointer
- * in case of failure, otherwise a valid
- * (ETERM *) pointer containing the reply
- */
int ei_rpc(ei_cnode* ec, int fd, char *mod, char *fun,
const char* inbuf, int inbuflen, ei_x_buff* x)
{
@@ -1640,27 +1924,45 @@ int ei_rpc(ei_cnode* ec, int fd, char *mod, char *fun,
char rex[MAXATOMLEN];
if (ei_rpc_to(ec, fd, mod, fun, inbuf, inbuflen) < 0) {
- return -1;
+ return ERL_ERROR;
}
- /* FIXME are we not to reply to the tick? */
+
+ /* ei_rpc_from() responds with a tick if it gets one... */
while ((i = ei_rpc_from(ec, fd, ERL_NO_TIMEOUT, &msg, x)) == ERL_TICK)
;
- if (i == ERL_ERROR) return -1;
- /*ep = 'erl'_element(2,emsg.msg);*/ /* {RPC_Tag, RPC_Reply} */
+ if (i == ERL_ERROR) return i;
+
+ /* Expect: {rex, RPC_Reply} */
+
index = 0;
- if (ei_decode_version(x->buff, &index, &i) < 0
- || ei_decode_ei_term(x->buff, &index, &t) < 0)
- return -1; /* FIXME ei_decode_version don't set erl_errno as before */
- /* FIXME this is strange, we don't check correct "rex" atom
- and we let it pass if not ERL_SMALL_TUPLE_EXT and arity == 2 */
- if (t.ei_type == ERL_SMALL_TUPLE_EXT && t.arity == 2)
- if (ei_decode_atom(x->buff, &index, rex) < 0)
- return -1;
+ if (ei_decode_version(x->buff, &index, &i) < 0)
+ goto ebadmsg;
+
+ if (ei_decode_ei_term(x->buff, &index, &t) < 0)
+ goto ebadmsg;
+
+ if (t.ei_type != ERL_SMALL_TUPLE_EXT && t.ei_type != ERL_LARGE_TUPLE_EXT)
+ goto ebadmsg;
+
+ if (t.arity != 2)
+ goto ebadmsg;
+
+ if (ei_decode_atom(x->buff, &index, rex) < 0)
+ goto ebadmsg;
+
+ if (strcmp("rex", rex) != 0)
+ goto ebadmsg;
+
/* remove header */
x->index -= index;
memmove(x->buff, &x->buff[index], x->index);
return 0;
+
+ebadmsg:
+
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
}
diff --git a/lib/erl_interface/src/connect/eirecv.c b/lib/erl_interface/src/connect/eirecv.c
index 0673d25d38..96732db5b6 100644
--- a/lib/erl_interface/src/connect/eirecv.c
+++ b/lib/erl_interface/src/connect/eirecv.c
@@ -70,7 +70,7 @@ ei_recv_internal (int fd,
err = EI_GET_CBS_CTX__(&cbs, &ctx, fd);
if (err) {
EI_CONN_SAVE_ERRNO__(err);
- return -1;
+ return ERL_ERROR;
}
/* get length field */
@@ -80,7 +80,7 @@ ei_recv_internal (int fd,
err = EIO;
if (err) {
EI_CONN_SAVE_ERRNO__(err);
- return -1;
+ return ERL_ERROR;
}
len = get32be(s);
@@ -91,7 +91,7 @@ ei_recv_internal (int fd,
ssize_t wlen = sizeof(tock);
ei_write_fill_ctx_t__(cbs, ctx, tock, &wlen, tmo); /* Failure no problem */
*msglenp = 0;
- return 0; /* maybe flag ERL_EAGAIN [sverkerw] */
+ return ERL_TICK;
}
/* turn off tracing on each receive. it will be turned back on if
@@ -106,7 +106,7 @@ ei_recv_internal (int fd,
err = EIO;
if (err) {
EI_CONN_SAVE_ERRNO__(err);
- return -1;
+ return ERL_ERROR;
}
/* now decode header */
@@ -119,8 +119,8 @@ ei_recv_internal (int fd,
|| ei_decode_tuple_header(header,&index,&arity)
|| ei_decode_long(header,&index,&msg->msgtype))
{
- erl_errno = EIO; /* Maybe another code for decoding errors */
- return -1;
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
}
switch (msg->msgtype) {
@@ -129,8 +129,8 @@ ei_recv_internal (int fd,
if (ei_decode_atom_as(header,&index,msg->cookie,sizeof(msg->cookie),ERLANG_UTF8,NULL,NULL)
|| ei_decode_pid(header,&index,&msg->to))
{
- erl_errno = EIO;
- return -1;
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
}
break;
@@ -141,8 +141,8 @@ ei_recv_internal (int fd,
|| ei_decode_atom_as(header,&index,msg->cookie,sizeof(msg->cookie),ERLANG_UTF8,NULL,NULL)
|| ei_decode_atom_as(header,&index,msg->toname,sizeof(msg->toname),ERLANG_UTF8,NULL,NULL))
{
- erl_errno = EIO;
- return -1;
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
}
/* actual message is remaining part of headerbuf, plus any unread bytes */
@@ -155,8 +155,8 @@ ei_recv_internal (int fd,
if (ei_decode_pid(header,&index,&msg->from)
|| ei_decode_pid(header,&index,&msg->to))
{
- erl_errno = EIO;
- return -1;
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
}
break;
@@ -167,8 +167,8 @@ ei_recv_internal (int fd,
if (ei_decode_pid(header,&index,&msg->from)
|| ei_decode_pid(header,&index,&msg->to))
{
- erl_errno = EIO;
- return -1;
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
}
break;
@@ -179,8 +179,8 @@ ei_recv_internal (int fd,
|| ei_decode_pid(header,&index,&msg->to)
|| ei_decode_trace(header,&index,&msg->token))
{
- erl_errno = EIO;
- return -1;
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
}
ei_trace(1,&msg->token); /* turn on tracing */
@@ -193,8 +193,8 @@ ei_recv_internal (int fd,
|| ei_decode_atom_as(header,&index,msg->toname,sizeof(msg->toname),ERLANG_UTF8,NULL,NULL)
|| ei_decode_trace(header,&index,&msg->token))
{
- erl_errno = EIO;
- return -1;
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
}
ei_trace(1,&msg->token); /* turn on tracing */
@@ -207,8 +207,8 @@ ei_recv_internal (int fd,
|| ei_decode_pid(header,&index,&msg->to)
|| ei_decode_trace(header,&index,&msg->token))
{
- erl_errno = EIO;
- return -1;
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
}
ei_trace(1,&msg->token); /* turn on tracing */
@@ -235,14 +235,14 @@ ei_recv_internal (int fd,
err = ei_read_fill_ctx_t__(cbs, ctx, header, &rlen, tmo);
if (err) {
EI_CONN_SAVE_ERRNO__(err);
- return -1;
+ return ERL_ERROR;
}
if (rlen == 0)
break;
remain -= rlen;
}
erl_errno = EMSGSIZE;
- return -1;
+ return ERL_ERROR;
}
else {
/* Dynamic buffer --- grow it. */
@@ -253,7 +253,7 @@ ei_recv_internal (int fd,
if ((mbuf = realloc(*mbufp, msglen)) == NULL)
{
erl_errno = ENOMEM;
- return -1;
+ return ERL_ERROR;
}
*mbufp = mbuf;
@@ -276,7 +276,7 @@ ei_recv_internal (int fd,
if (err) {
*msglenp = bytesread-index+1; /* actual bytes in users buffer */
EI_CONN_SAVE_ERRNO__(err);
- return -1;
+ return ERL_ERROR;
}
}
diff --git a/lib/erl_interface/src/connect/send.c b/lib/erl_interface/src/connect/send.c
index f9337187ab..df2e3e023d 100644
--- a/lib/erl_interface/src/connect/send.c
+++ b/lib/erl_interface/src/connect/send.c
@@ -67,19 +67,31 @@ int ei_send_encoded_tmo(int fd, const erlang_pid *to,
/* check that he can receive trace tokens first */
if (ei_distversion(fd) > 0) token = ei_trace(0,NULL);
+ EI_CONN_SAVE_ERRNO__(EINVAL);
+
/* header = SEND, cookie, to max sizes: */
- ei_encode_version(header,&index); /* 1 */
+ if (ei_encode_version(header,&index) < 0) /* 1 */
+ return ERL_ERROR;
if (token) {
- ei_encode_tuple_header(header,&index,4); /* 2 */
- ei_encode_long(header,&index,ERL_SEND_TT); /* 2 */
+ if (ei_encode_tuple_header(header,&index,4) < 0) /* 2 */
+ return ERL_ERROR;
+ if (ei_encode_long(header,&index,ERL_SEND_TT) < 0) /* 2 */
+ return ERL_ERROR;
} else {
- ei_encode_tuple_header(header,&index,3);
- ei_encode_long(header,&index,ERL_SEND);
+ if (ei_encode_tuple_header(header,&index,3) < 0)
+ return ERL_ERROR;
+ if (ei_encode_long(header,&index,ERL_SEND) < 0)
+ return ERL_ERROR;
}
- ei_encode_atom(header,&index,ei_getfdcookie(fd)); /* 258 */
- ei_encode_pid(header,&index,to); /* 268 */
+ if (ei_encode_atom(header,&index,ei_getfdcookie(fd)) < 0) /* 258 */
+ return ERL_ERROR;
+ if (ei_encode_pid(header,&index,to) < 0) /* 268 */
+ return ERL_ERROR;
- if (token) ei_encode_trace(header,&index,token); /* 534 */
+ if (token) {
+ if (ei_encode_trace(header,&index,token) < 0) /* 534 */
+ return ERL_ERROR;
+ }
/* control message (precedes header actually) */
/* length = 1 ('p') + header len + message len */
@@ -107,9 +119,11 @@ int ei_send_encoded_tmo(int fd, const erlang_pid *to,
err = EIO;
if (err) {
EI_CONN_SAVE_ERRNO__(err);
- return -1;
+ return ERL_ERROR;
}
+ erl_errno = 0;
+
return 0;
}
#endif /* EI_HAVE_STRUCT_IOVEC__ */
@@ -121,7 +135,7 @@ int ei_send_encoded_tmo(int fd, const erlang_pid *to,
err = EIO;
if (err) {
EI_CONN_SAVE_ERRNO__(err);
- return -1;
+ return ERL_ERROR;
}
len = tot_len = (ssize_t) msglen;
@@ -130,9 +144,10 @@ int ei_send_encoded_tmo(int fd, const erlang_pid *to,
err = EIO;
if (err) {
EI_CONN_SAVE_ERRNO__(err);
- return -1;
+ return ERL_ERROR;
}
+ erl_errno = 0;
return 0;
}
diff --git a/lib/erl_interface/src/connect/send_exit.c b/lib/erl_interface/src/connect/send_exit.c
index af0fb2af88..231aad0ae0 100644
--- a/lib/erl_interface/src/connect/send_exit.c
+++ b/lib/erl_interface/src/connect/send_exit.c
@@ -67,9 +67,12 @@ int ei_send_exit_tmo(int fd, const erlang_pid *from, const erlang_pid *to,
return ERL_ERROR;
}
- if (len > EISMALLBUF)
- if (!(dbuf = malloc(len)))
- return -1;
+ if (len > EISMALLBUF) {
+ if (!(dbuf = malloc(len))) {
+ EI_CONN_SAVE_ERRNO__(ENOMEM);
+ return ERL_ERROR;
+ }
+ }
msgbuf = (dbuf ? dbuf : sbuf);
@@ -77,31 +80,46 @@ int ei_send_exit_tmo(int fd, const erlang_pid *from, const erlang_pid *to,
/* check that he can receive trace tokens first */
if (ei_distversion(fd) > 0) token = ei_trace(0,NULL);
+ EI_CONN_SAVE_ERRNO__(EINVAL);
+
index = 5; /* max sizes: */
- ei_encode_version(msgbuf,&index); /* 1 */
+ if (ei_encode_version(msgbuf,&index) < 0) /* 1 */
+ return ERL_ERROR;
if (token) {
- ei_encode_tuple_header(msgbuf,&index,5); /* 2 */
- ei_encode_long(msgbuf,&index,ERL_EXIT_TT); /* 2 */
+ if (ei_encode_tuple_header(msgbuf,&index,5) < 0) /* 2 */
+ return ERL_ERROR;
+ if (ei_encode_long(msgbuf,&index,ERL_EXIT_TT) < 0)/* 2 */
+ return ERL_ERROR;
}
else {
- ei_encode_tuple_header(msgbuf,&index,4);
- ei_encode_long(msgbuf,&index,ERL_EXIT);
+ if (ei_encode_tuple_header(msgbuf,&index,4) < 0)
+ return ERL_ERROR;
+ if (ei_encode_long(msgbuf,&index,ERL_EXIT) < 0)
+ return ERL_ERROR;
}
- ei_encode_pid(msgbuf,&index,from); /* 268 */
- ei_encode_pid(msgbuf,&index,to); /* 268 */
+ if (ei_encode_pid(msgbuf,&index,from) < 0) /* 268 */
+ return ERL_ERROR;
+ if (ei_encode_pid(msgbuf,&index,to) < 0) /* 268 */
+ return ERL_ERROR;
- if (token) ei_encode_trace(msgbuf,&index,token); /* 534 */
+ if (token) {
+ if (ei_encode_trace(msgbuf,&index,token) < 0) /* 534 */
+ return ERL_ERROR;
+ }
/* Reason */
- ei_encode_string(msgbuf,&index,reason); /* len */
+ if (ei_encode_string(msgbuf,&index,reason) < 0) /* len */
+ return ERL_ERROR;
/* 5 byte header missing */
s = msgbuf;
put32be(s, index - 4); /* 4 */
- put8(s, ERL_PASS_THROUGH); /* 1 */
+ put8(s, ERL_PASS_THROUGH); /* 1 */
/*** sum: len + 1080 */
- if (ei_tracelevel >= 4)
- ei_show_sendmsg(stderr,msgbuf,NULL);
+ if (ei_tracelevel >= 4) {
+ if (ei_show_sendmsg(stderr,msgbuf,NULL) < 0)
+ return ERL_ERROR;
+ }
wlen = (ssize_t) index;
err = ei_write_fill_ctx_t__(cbs, ctx, msgbuf, &wlen, tmo);
@@ -113,6 +131,8 @@ int ei_send_exit_tmo(int fd, const erlang_pid *from, const erlang_pid *to,
EI_CONN_SAVE_ERRNO__(err);
return ERL_ERROR;
}
+
+ erl_errno = 0;
return 0;
}
diff --git a/lib/erl_interface/src/connect/send_reg.c b/lib/erl_interface/src/connect/send_reg.c
index b5c9e6a6f8..1d9c85f210 100644
--- a/lib/erl_interface/src/connect/send_reg.c
+++ b/lib/erl_interface/src/connect/send_reg.c
@@ -64,21 +64,33 @@ int ei_send_reg_encoded_tmo(int fd, const erlang_pid *from,
if (ei_distversion(fd) > 0)
token = ei_trace(0,NULL);
+ EI_CONN_SAVE_ERRNO__(EINVAL);
+
/* header = REG_SEND, from, cookie, toname max sizes: */
- ei_encode_version(header,&index); /* 1 */
+ if (ei_encode_version(header,&index) < 0) /* 1 */
+ return ERL_ERROR;
if (token) {
- ei_encode_tuple_header(header,&index,5); /* 2 */
- ei_encode_long(header,&index,ERL_REG_SEND_TT); /* 2 */
+ if (ei_encode_tuple_header(header,&index,5) < 0) /* 2 */
+ return ERL_ERROR;
+ if (ei_encode_long(header,&index,ERL_REG_SEND_TT) < 0) /* 2 */
+ return ERL_ERROR;
} else {
- ei_encode_tuple_header(header,&index,4);
- ei_encode_long(header,&index,ERL_REG_SEND);
+ if (ei_encode_tuple_header(header,&index,4) < 0)
+ return ERL_ERROR;
+ if (ei_encode_long(header,&index,ERL_REG_SEND) < 0)
+ return ERL_ERROR;
}
- ei_encode_pid(header, &index, from); /* 268 */
- ei_encode_atom(header, &index, ei_getfdcookie(fd)); /* 258 */
- ei_encode_atom(header, &index, to); /* 268 */
-
- if (token) ei_encode_trace(header,&index,token); /* 534 */
+ if (ei_encode_pid(header, &index, from) < 0) /* 268 */
+ return ERL_ERROR;
+ if (ei_encode_atom(header, &index, ei_getfdcookie(fd)) < 0) /* 258 */
+ return ERL_ERROR;
+ if (ei_encode_atom(header, &index, to) < 0) /* 268 */
+ return ERL_ERROR;
+ if (token) {
+ if (ei_encode_trace(header,&index,token) < 0) /* 534 */
+ return ERL_ERROR;
+ }
/* control message (precedes header actually) */
/* length = 1 ('p') + header len + message len */
s = header;
@@ -103,8 +115,11 @@ int ei_send_reg_encoded_tmo(int fd, const erlang_pid *from,
err = EIO;
if (err) {
EI_CONN_SAVE_ERRNO__(err);
- return -1;
+ return ERL_ERROR;
}
+
+ erl_errno = 0;
+
return 0;
}
#endif /* EI_HAVE_STRUCT_IOVEC__ */
@@ -116,7 +131,7 @@ int ei_send_reg_encoded_tmo(int fd, const erlang_pid *from,
err = EIO;
if (err) {
EI_CONN_SAVE_ERRNO__(err);
- return -1;
+ return ERL_ERROR;
}
len = tot_len = (ssize_t) msglen;
@@ -125,8 +140,10 @@ int ei_send_reg_encoded_tmo(int fd, const erlang_pid *from,
err = EIO;
if (err) {
EI_CONN_SAVE_ERRNO__(err);
- return -1;
+ return ERL_ERROR;
}
+
+ erl_errno = 0;
return 0;
}
diff --git a/lib/erl_interface/src/decode/decode_iodata.c b/lib/erl_interface/src/decode/decode_iodata.c
new file mode 100644
index 0000000000..88d23d8a1e
--- /dev/null
+++ b/lib/erl_interface/src/decode/decode_iodata.c
@@ -0,0 +1,166 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 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%
+ *
+ */
+
+#include <string.h>
+#include "eidef.h"
+#include "eiext.h"
+#include "putget.h"
+
+static int decode_list_ext_iodata(const char *buf, int* index,
+ int *szp, unsigned char **pp);
+static void decode_string(const char *buf, int* index,
+ int *szp, unsigned char **pp);
+
+int ei_decode_iodata(const char *buf, int* index, int *szp, char *out_buf)
+{
+ int type, len;
+
+ if (szp)
+ *szp = 0;
+
+ if (ei_get_type(buf, index, &type, &len) < 0)
+ return -1;
+
+ /* Top level of iodata is either a list or a binary... */
+
+ switch (type) {
+
+ case ERL_BINARY_EXT: {
+ long llen;
+ if (ei_decode_binary(buf, index, out_buf, &llen) < 0)
+ return -1;
+ if (llen != (long) len)
+ return -1; /* general 64-bit issue with ei api... */
+ if (szp)
+ *szp += len;
+ return 0;
+ }
+
+ case ERL_STRING_EXT: {
+ unsigned char *p = (unsigned char *) out_buf;
+ decode_string(buf, index, szp, p ? &p : NULL);
+ return 0;
+ }
+ case ERL_NIL_EXT:
+ return ei_decode_list_header(buf, index, NULL);
+
+ case ERL_LIST_EXT: {
+ unsigned char *ptr = (unsigned char *) out_buf;
+ len = 0;
+ return decode_list_ext_iodata(buf, index,
+ szp ? szp : &len,
+ ptr ? &ptr : NULL);
+ }
+
+ default:
+ return -1; /* Not a list nor a binary... */
+ }
+}
+
+static int decode_list_ext_iodata(const char *buf, int* index,
+ int *szp, unsigned char **pp)
+{
+ int type, len, i, conses;
+
+ if (ei_decode_list_header(buf, index, &conses) < 0)
+ return -1;
+
+ for (i = 0; i <= conses; i++) {
+
+ if (ei_get_type(buf, index, &type, &len) < 0)
+ return -1;
+
+ switch (type) {
+ case ERL_SMALL_INTEGER_EXT:
+ case ERL_INTEGER_EXT: {
+ long val;
+ if (i == conses)
+ return -1; /* int not allowed in cdr of cons */
+ if (ei_decode_long(buf, index, &val) < 0)
+ return -1;
+ if (val < 0 || 255 < val)
+ return -1;
+ if (pp)
+ *((*pp)++) = (unsigned char) val;
+ *szp += 1;
+ break;
+ }
+ case ERL_BINARY_EXT: {
+ void *p = pp ? *pp : NULL;
+ long llen;
+ if (ei_decode_binary(buf, index, p, &llen) < 0)
+ return -1;
+ if (llen != (long) len)
+ return -1; /* general 64-bit issue with ei api... */
+ if (pp)
+ *pp += len;
+ *szp += len;
+ break;
+ }
+ case ERL_STRING_EXT:
+ decode_string(buf, index, szp, pp);
+ break;
+ case ERL_LIST_EXT:
+ if (decode_list_ext_iodata(buf, index, szp, pp) < 0)
+ return -1;
+ break;
+ case ERL_NIL_EXT:
+ if (ei_decode_list_header(buf, index, NULL) < 0)
+ return -1;
+ break;
+ default:
+ /* Not a list, a binary, nor a byte sized integer... */
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void
+decode_string(const char *buf, int* index, int *szp, unsigned char **pp)
+{
+ /*
+ * ei_decode_string() null-terminates the string
+ * which we do not want, so we decode it ourselves
+ * here instead...
+ */
+ int len;
+ char *s = (char *) buf + *index;
+ char *s0 = s;
+
+ /* ASSERT(*s == ERL_STRING_EXT); */
+ s++;
+
+ len = get16be(s);
+
+ if (pp) {
+ memcpy(*pp, s, len);
+ *pp += len;
+ }
+
+ if (szp)
+ *szp += len;
+
+ s += len;
+ *index += s-s0;
+
+}
diff --git a/lib/erl_interface/src/decode/decode_ref.c b/lib/erl_interface/src/decode/decode_ref.c
index c9b38c1c3b..c10a02094e 100644
--- a/lib/erl_interface/src/decode/decode_ref.c
+++ b/lib/erl_interface/src/decode/decode_ref.c
@@ -54,6 +54,9 @@ int ei_decode_ref(const char *buf, int *index, erlang_ref *p)
/* first the integer count */
count = get16be(s);
+ if (count > sizeof(p->n)/sizeof(p->n[0]))
+ return -1; /* Not enough space in struct... */
+
if (p) {
p->len = count;
if (get_atom(&s, p->node, NULL) < 0) return -1;
diff --git a/lib/erl_interface/src/encode/encode_trace.c b/lib/erl_interface/src/encode/encode_trace.c
index d0e6aec474..4c51e3d84b 100644
--- a/lib/erl_interface/src/encode/encode_trace.c
+++ b/lib/erl_interface/src/encode/encode_trace.c
@@ -23,12 +23,18 @@
int ei_encode_trace(char *buf, int *index, const erlang_trace *p)
{
/* { Flags, Label, Serial, FromPid, Prev } */
- ei_encode_tuple_header(buf,index,5);
- ei_encode_long(buf,index,p->flags);
- ei_encode_long(buf,index,p->label);
- ei_encode_long(buf,index,p->serial);
- ei_encode_pid(buf,index,&p->from);
- ei_encode_long(buf,index,p->prev);
+ if (ei_encode_tuple_header(buf,index,5) < 0)
+ return -1;
+ if (ei_encode_long(buf,index,p->flags) < 0)
+ return -1;
+ if (ei_encode_long(buf,index,p->label) < 0)
+ return -1;
+ if (ei_encode_long(buf,index,p->serial) < 0)
+ return -1;
+ if (ei_encode_pid(buf,index,&p->from) < 0)
+ return -1;
+ if (ei_encode_long(buf,index,p->prev) < 0)
+ return -1;
/* index is updated by the functions we called */
diff --git a/lib/erl_interface/src/global/global_names.c b/lib/erl_interface/src/global/global_names.c
index bcbe740223..4524439467 100644
--- a/lib/erl_interface/src/global/global_names.c
+++ b/lib/erl_interface/src/global/global_names.c
@@ -26,6 +26,7 @@
#include "ei_connect_int.h"
#include "ei.h"
#include "ei_connect.h"
+#include "ei_internal.h"
#define GLOBALNAMEBUF (16*1024) /* not very small actually */
@@ -52,16 +53,18 @@ char **ei_global_names(ei_cnode *ec, int fd, int *count)
char **names;
char *s;
- self->num = fd;
- ei_encode_version(buf,&index);
- ei_encode_tuple_header(buf,&index,2);
- ei_encode_pid(buf,&index,self); /* PidFrom */
- ei_encode_tuple_header(buf,&index,5);
- ei_encode_atom(buf,&index,"call"); /* call */
- ei_encode_atom(buf,&index,"global"); /* Mod */
- ei_encode_atom(buf,&index,"registered_names"); /* Fun */
- ei_encode_list_header(buf,&index,0); /* Args: [ ] */
- ei_encode_atom(buf,&index,"user"); /* user */
+ if (ei_encode_version(buf,&index)
+ || ei_encode_tuple_header(buf,&index,2)
+ || ei_encode_pid(buf,&index,self) /* PidFrom */
+ || ei_encode_tuple_header(buf,&index,5)
+ || ei_encode_atom(buf,&index,"call") /* call */
+ || ei_encode_atom(buf,&index,"global") /* Mod */
+ || ei_encode_atom(buf,&index,"registered_names")/* Fun */
+ || ei_encode_list_header(buf,&index,0) /* Args: [ ] */
+ || ei_encode_atom(buf,&index,"user")) { /* user */
+ EI_CONN_SAVE_ERRNO__(EINVAL);
+ return NULL;
+ }
/* make the rpc call */
if (ei_send_reg_encoded(fd,self,"rex",buf,index)) return NULL;
@@ -72,7 +75,10 @@ char **ei_global_names(ei_cnode *ec, int fd, int *count)
else break;
}
- if (i != ERL_SEND) return NULL;
+ if (i != ERL_SEND) {
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return NULL;
+ }
/* expecting { rex, [name1, name2, ...] } */
size = msglen;
@@ -83,7 +89,10 @@ char **ei_global_names(ei_cnode *ec, int fd, int *count)
|| (arity != 2)
|| ei_decode_atom(buf,&index,tmpbuf)
|| strcmp(tmpbuf,"rex")
- || ei_decode_list_header(buf,&index,&arity)) return NULL;
+ || ei_decode_list_header(buf,&index,&arity)) {
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return NULL;
+ }
/* we use the size of the rest of the received message to estimate
@@ -92,7 +101,10 @@ char **ei_global_names(ei_cnode *ec, int fd, int *count)
* a little less than the atoms themselves needed in the reply.
*/
arity++; /* we will need a terminating NULL as well */
- if (!(names = malloc((arity * sizeof(char**)) + (size-index)))) return NULL;
+ if (!(names = malloc((arity * sizeof(char**)) + (size-index)))) {
+ EI_CONN_SAVE_ERRNO__(ENOMEM);
+ return NULL;
+ }
/* arity pointers first, followed by s */
s = (char *)(names+arity);
diff --git a/lib/erl_interface/src/global/global_register.c b/lib/erl_interface/src/global/global_register.c
index c260ce091c..dbdab8dbc5 100644
--- a/lib/erl_interface/src/global/global_register.c
+++ b/lib/erl_interface/src/global/global_register.c
@@ -23,6 +23,7 @@
#include "eisend.h"
#include "eirecv.h"
#include "ei.h"
+#include "ei_internal.h"
int ei_global_register(int fd, const char *name, erlang_pid *self)
{
@@ -40,24 +41,27 @@ int ei_global_register(int fd, const char *name, erlang_pid *self)
/* set up rpc arguments */
/* { PidFrom, { call, Mod, Fun, Args, user }} */
index = 0;
- ei_encode_version(buf,&index);
- ei_encode_tuple_header(buf,&index,2);
- ei_encode_pid(buf,&index,self); /* PidFrom */
- ei_encode_tuple_header(buf,&index,5);
- ei_encode_atom(buf,&index,"call"); /* call */
- ei_encode_atom(buf,&index,"global"); /* Mod */
- ei_encode_atom(buf,&index,"register_name_external"); /* Fun */
- ei_encode_list_header(buf,&index,3); /* Args: [ name, self(), cnode ] */
- ei_encode_atom(buf,&index,name);
- ei_encode_pid(buf,&index,self);
- ei_encode_tuple_header(buf,&index,2);
- ei_encode_atom(buf,&index,"global"); /* special "resolve" treatment */
- ei_encode_atom(buf,&index,"cnode"); /* i.e. we get a SEND when conflict */
- ei_encode_empty_list(buf,&index);
- ei_encode_atom(buf,&index,"user"); /* user */
+ if (ei_encode_version(buf,&index)
+ || ei_encode_tuple_header(buf,&index,2)
+ || ei_encode_pid(buf,&index,self) /* PidFrom */
+ || ei_encode_tuple_header(buf,&index,5)
+ || ei_encode_atom(buf,&index,"call") /* call */
+ || ei_encode_atom(buf,&index,"global") /* Mod */
+ || ei_encode_atom(buf,&index,"register_name_external")/* Fun */
+ || ei_encode_list_header(buf,&index,3) /* Args: [ name, self(), cnode ] */
+ || ei_encode_atom(buf,&index,name)
+ || ei_encode_pid(buf,&index,self)
+ || ei_encode_tuple_header(buf,&index,2)
+ || ei_encode_atom(buf,&index,"global") /* special "resolve" treatment */
+ || ei_encode_atom(buf,&index,"cnode") /* i.e. we get a SEND when conflict */
+ || ei_encode_empty_list(buf,&index)
+ || ei_encode_atom(buf,&index,"user")) { /* user */
+ EI_CONN_SAVE_ERRNO__(EINVAL);
+ return ERL_ERROR;
+ }
/* make the rpc call */
- if (ei_send_reg_encoded(fd,self,"rex",buf,index)) return -1;
+ if (ei_send_reg_encoded(fd,self,"rex",buf,index)) return ERL_ERROR;
/* get the reply: expect link and an atom, or just an atom */
needlink = needatom = needmonitor = 1;
@@ -72,19 +76,28 @@ int ei_global_register(int fd, const char *name, erlang_pid *self)
switch (i) {
case ERL_LINK:
/* got link */
- if (!needlink) return -1;
+ if (!needlink) {
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
+ }
needlink = 0;
break;
case ERL_MONITOR_P-10:
/* got monitor */
- if (!needmonitor) { return -1;}
+ if (!needmonitor) {
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
+ }
needmonitor = 0;
break;
case ERL_SEND:
/* got message - does it contain our atom? */
- if (!needatom) return -1;
+ if (!needatom) {
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
+ }
else {
/* expecting { rex, yes } */
index = 0;
@@ -94,8 +107,10 @@ int ei_global_register(int fd, const char *name, erlang_pid *self)
|| ei_decode_atom(buf,&index,tmpbuf)
|| strcmp(tmpbuf,"rex")
|| ei_decode_atom(buf,&index,tmpbuf)
- || strcmp(tmpbuf,"yes"))
- return -1; /* bad response from other side */
+ || strcmp(tmpbuf,"yes")) {
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR; /* bad response from other side */
+ }
/* we're done */
return 0;
@@ -103,7 +118,8 @@ int ei_global_register(int fd, const char *name, erlang_pid *self)
break;
default:
- return -1; /* something else */
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR; /* something else */
}
}
return 0;
diff --git a/lib/erl_interface/src/global/global_unregister.c b/lib/erl_interface/src/global/global_unregister.c
index ee785a2abd..4743fe65d7 100644
--- a/lib/erl_interface/src/global/global_unregister.c
+++ b/lib/erl_interface/src/global/global_unregister.c
@@ -25,6 +25,7 @@
#include "ei_connect_int.h"
#include "ei_connect.h"
#include "ei.h"
+#include "ei_internal.h"
/* remove the association between name and its pid */
/* global:unregister_name(name) -> ok */
@@ -40,22 +41,23 @@ int ei_global_unregister(ei_cnode *ec, int fd, const char *name)
int version,arity,msglen;
int needunlink, needatom, needdemonitor;
- /* make a self pid */
- self->num = fd;
- ei_encode_version(buf,&index);
- ei_encode_tuple_header(buf,&index,2);
- ei_encode_pid(buf,&index,self); /* PidFrom */
- ei_encode_tuple_header(buf,&index,5);
- ei_encode_atom(buf,&index,"call"); /* call */
- ei_encode_atom(buf,&index,"global"); /* Mod */
- ei_encode_atom(buf,&index,"unregister_name_external"); /* Fun */
- ei_encode_list_header(buf,&index,1); /* Args: [ name ] */
- ei_encode_atom(buf,&index,name);
- ei_encode_empty_list(buf,&index);
- ei_encode_atom(buf,&index,"user"); /* user */
+ if (ei_encode_version(buf,&index)
+ || ei_encode_tuple_header(buf,&index,2)
+ || ei_encode_pid(buf,&index,self) /* PidFrom */
+ || ei_encode_tuple_header(buf,&index,5)
+ || ei_encode_atom(buf,&index,"call") /* call */
+ || ei_encode_atom(buf,&index,"global") /* Mod */
+ || ei_encode_atom(buf,&index,"unregister_name_external") /* Fun */
+ || ei_encode_list_header(buf,&index,1) /* Args: [ name ] */
+ || ei_encode_atom(buf,&index,name)
+ || ei_encode_empty_list(buf,&index)
+ || ei_encode_atom(buf,&index,"user")) { /* user */
+ EI_CONN_SAVE_ERRNO__(EINVAL);
+ return ERL_ERROR;
+ }
/* make the rpc call */
- if (ei_send_reg_encoded(fd,self,"rex",buf,index)) return -1;
+ if (ei_send_reg_encoded(fd,self,"rex",buf,index)) return ERL_ERROR;
/* get the reply: expect unlink and an atom, or just an atom */
needunlink = needatom = needdemonitor = 1;
@@ -70,19 +72,28 @@ int ei_global_unregister(ei_cnode *ec, int fd, const char *name)
switch (i) {
case ERL_UNLINK:
/* got unlink */
- if (!needunlink) return -1;
+ if (!needunlink) {
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
+ }
needunlink = 0;
break;
case ERL_DEMONITOR_P-10:
/* got demonitor */
- if (!needdemonitor) return -1;
+ if (!needdemonitor) {
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
+ }
needdemonitor = 0;
break;
case ERL_SEND:
/* got message - does it contain our atom? */
- if (!needatom) return -1;
+ if (!needatom) {
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
+ }
else {
/* expecting { rex, ok } */
index = 0;
@@ -92,8 +103,10 @@ int ei_global_unregister(ei_cnode *ec, int fd, const char *name)
|| ei_decode_atom(buf,&index,tmpbuf)
|| strcmp(tmpbuf,"rex")
|| ei_decode_atom(buf,&index,tmpbuf)
- || strcmp(tmpbuf,"ok"))
- return -1; /* bad response from other side */
+ || strcmp(tmpbuf,"ok")) {
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR; /* bad response from other side */
+ }
/* we're done here */
return 0;
@@ -101,7 +114,8 @@ int ei_global_unregister(ei_cnode *ec, int fd, const char *name)
break;
default:
- return -1;
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
}
}
diff --git a/lib/erl_interface/src/global/global_whereis.c b/lib/erl_interface/src/global/global_whereis.c
index afedc98030..5a6134c2ab 100644
--- a/lib/erl_interface/src/global/global_whereis.c
+++ b/lib/erl_interface/src/global/global_whereis.c
@@ -26,6 +26,7 @@
#include "ei_connect_int.h"
#include "ei.h"
#include "ei_connect.h"
+#include "ei_internal.h"
/* return the ETERM pid corresponding to name. If caller
* provides non-NULL node, nodename will be returned there
@@ -44,22 +45,22 @@ int ei_global_whereis(ei_cnode *ec, int fd, const char *name, erlang_pid* pid, c
int i;
int version,arity,msglen;
- self->num = fd; /* FIXME looks strange to change something?! */
-
- ei_encode_version(buf,&index);
- ei_encode_tuple_header(buf,&index,2);
- ei_encode_pid(buf,&index,self); /* PidFrom */
- ei_encode_tuple_header(buf,&index,5);
- ei_encode_atom(buf,&index,"call"); /* call */
- ei_encode_atom(buf,&index,"global"); /* Mod */
- ei_encode_atom(buf,&index,"whereis_name"); /* Fun */
- ei_encode_list_header(buf,&index,1); /* Args: [ name ] */
- ei_encode_atom(buf,&index,name);
- ei_encode_empty_list(buf,&index);
- ei_encode_atom(buf,&index,"user"); /* user */
-
+ if (ei_encode_version(buf,&index)
+ || ei_encode_tuple_header(buf,&index,2)
+ || ei_encode_pid(buf,&index,self) /* PidFrom */
+ || ei_encode_tuple_header(buf,&index,5)
+ || ei_encode_atom(buf,&index,"call") /* call */
+ || ei_encode_atom(buf,&index,"global") /* Mod */
+ || ei_encode_atom(buf,&index,"whereis_name") /* Fun */
+ || ei_encode_list_header(buf,&index,1) /* Args: [ name ] */
+ || ei_encode_atom(buf,&index,name)
+ || ei_encode_empty_list(buf,&index)
+ || ei_encode_atom(buf,&index,"user")) { /* user */
+ EI_CONN_SAVE_ERRNO__(EINVAL);
+ return ERL_ERROR;
+ }
/* make the rpc call */
- if (ei_send_reg_encoded(fd,self,"rex",buf,index)) return -1;
+ if (ei_send_reg_encoded(fd,self,"rex",buf,index)) return ERL_ERROR;
while (1) {
index = EISMALLBUF;
@@ -67,7 +68,7 @@ int ei_global_whereis(ei_cnode *ec, int fd, const char *name, erlang_pid* pid, c
else break;
}
- if (i != ERL_SEND) return -1;
+ if (i != ERL_SEND) return ERL_ERROR;
/* expecting { rex, pid } */
index = 0;
@@ -76,8 +77,10 @@ int ei_global_whereis(ei_cnode *ec, int fd, const char *name, erlang_pid* pid, c
|| (arity != 2)
|| ei_decode_atom(buf,&index,tmpbuf)
|| strcmp(tmpbuf,"rex")
- || ei_decode_pid(buf,&index,&epid))
- return -1; /* bad response from other side */
+ || ei_decode_pid(buf,&index,&epid)) {
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR; /* bad response from other side */
+ }
/* extract the nodename for the caller */
if (node) {
@@ -86,7 +89,8 @@ int ei_global_whereis(ei_cnode *ec, int fd, const char *name, erlang_pid* pid, c
strcpy(node, node_str);
}
else {
- return -1;
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
}
}
*pid = epid;
diff --git a/lib/erl_interface/src/misc/ei_cmp_nc.c b/lib/erl_interface/src/misc/ei_cmp_nc.c
new file mode 100644
index 0000000000..73650f429f
--- /dev/null
+++ b/lib/erl_interface/src/misc/ei_cmp_nc.c
@@ -0,0 +1,117 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 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%
+ *
+ */
+
+#include <string.h>
+#include "eidef.h"
+
+/*
+ * Comparison of node container types (pid, port, ref). Comparison
+ * done the same way as ERTS compare them. In ref and port case,
+ * node info is compared before other data, and in pid case node
+ * info is compared after other data.
+ */
+
+static int cmp_nodes(char *aname, unsigned int acreation,
+ char *bname, unsigned int bcreation)
+{
+ int differ = strcmp(aname, bname);
+ if (differ)
+ return differ;
+ if (acreation == bcreation)
+ return 0;
+ if (acreation < bcreation)
+ return -1;
+ return 1;
+}
+
+int
+ei_cmp_refs(erlang_ref *a, erlang_ref *b)
+{
+ int differ;
+ int i, alen, blen;
+ unsigned int *anum, *bnum;
+
+ differ = cmp_nodes(a->node, a->creation, b->node, b->creation);
+ if (differ)
+ return differ;
+
+ alen = a->len;
+ blen = b->len;
+
+ anum = &a->n[0];
+ bnum = &b->n[0];
+
+ if (alen != blen) {
+ if (alen > blen) {
+ do {
+ if (anum[alen - 1] != 0)
+ return 1;
+ alen--;
+ } while (alen > blen);
+ }
+ else {
+ do {
+ if (bnum[blen - 1] != 0)
+ return -1;
+ blen--;
+ } while (alen < blen);
+ }
+ }
+
+ for (i = alen - 1; i >= 0; i--)
+ if (anum[i] != bnum[i])
+ return anum[i] < bnum[i] ? -1 : 1;
+
+ return 0;
+}
+
+int
+ei_cmp_pids(erlang_pid *a, erlang_pid *b)
+{
+ int differ;
+
+ if (a->serial != b->serial)
+ return a->serial < b->serial ? -1 : 1;
+
+ if (a->num != b->num)
+ return a->num < b->num ? -1 : 1;
+
+ differ = cmp_nodes(a->node, a->creation, b->node, b->creation);
+ if (differ)
+ return differ;
+
+ return 0;
+}
+
+int
+ei_cmp_ports(erlang_port *a, erlang_port *b)
+{
+ int differ;
+
+ differ = cmp_nodes(a->node, a->creation, b->node, b->creation);
+ if (differ)
+ return differ;
+
+ if (a->id != b->id)
+ return a->id < b->id ? -1 : 1;
+
+ return 0;
+}
diff --git a/lib/erl_interface/src/misc/ei_decode_term.c b/lib/erl_interface/src/misc/ei_decode_term.c
index 79c8fb4388..72f1e0d511 100644
--- a/lib/erl_interface/src/misc/ei_decode_term.c
+++ b/lib/erl_interface/src/misc/ei_decode_term.c
@@ -47,25 +47,30 @@ int ei_decode_ei_term(const char* buf, int* index, ei_term* term)
break;
case ERL_FLOAT_EXT:
case NEW_FLOAT_EXT:
+ term->ei_type = ERL_FLOAT_EXT;
return (ei_decode_double(buf, index, &term->value.d_val) < 0
? -1 : 1);
case ERL_ATOM_EXT:
case ERL_ATOM_UTF8_EXT:
case ERL_SMALL_ATOM_EXT:
case ERL_SMALL_ATOM_UTF8_EXT:
+ term->ei_type = ERL_ATOM_EXT;
return (ei_decode_atom(buf, index, term->value.atom_name) < 0
? -1 : 1);
case ERL_REFERENCE_EXT:
case ERL_NEW_REFERENCE_EXT:
case ERL_NEWER_REFERENCE_EXT:
+ term->ei_type = ERL_NEW_REFERENCE_EXT;
return (ei_decode_ref(buf, index, &term->value.ref) < 0
? -1 : 1);
case ERL_PORT_EXT:
case ERL_NEW_PORT_EXT:
+ term->ei_type = ERL_PORT_EXT;
return (ei_decode_port(buf, index, &term->value.port) < 0
? -1 : 1);
case ERL_PID_EXT:
case ERL_NEW_PID_EXT:
+ term->ei_type = ERL_PID_EXT;
return (ei_decode_pid(buf, index, &term->value.pid) < 0
? -1 : 1);
case ERL_SMALL_TUPLE_EXT:
diff --git a/lib/erl_interface/src/misc/ei_portio.h b/lib/erl_interface/src/misc/ei_portio.h
index d881a51398..1805411804 100644
--- a/lib/erl_interface/src/misc/ei_portio.h
+++ b/lib/erl_interface/src/misc/ei_portio.h
@@ -22,6 +22,8 @@
#ifndef _EI_PORTIO_H
#define _EI_PORTIO_H
+#include <stdint.h>
+
#undef EI_HAVE_STRUCT_IOVEC__
#if !defined(__WIN32__) && defined(HAVE_SYS_UIO_H)
/* Declaration of struct iovec *iov should be visible in this scope. */
@@ -50,12 +52,12 @@ int ei_socket_callbacks_have_writev__(ei_socket_callbacks *cbs);
extern ei_socket_callbacks ei_default_socket_callbacks;
#define EI_FD_AS_CTX__(FD) \
- ((void *) (long) (FD))
+ ((void *) (intptr_t) (FD))
#define EI_DFLT_CTX_TO_FD__(CTX, FD) \
- ((int) (long) (CTX) < 0 \
+ ((intptr_t) (CTX) < 0 \
? EBADF \
- : (*(FD) = (int) (long) (CTX), 0))
+ : (*(FD) = (int) (intptr_t) (CTX), 0))
#define EI_GET_FD__(CBS, CTX, FD) \
((CBS) == &ei_default_socket_callbacks \
diff --git a/lib/erl_interface/src/misc/show_msg.c b/lib/erl_interface/src/misc/show_msg.c
index 518d9dd595..c87e73c545 100644
--- a/lib/erl_interface/src/misc/show_msg.c
+++ b/lib/erl_interface/src/misc/show_msg.c
@@ -129,9 +129,9 @@ int ei_show_sendmsg(FILE *stream, const char *header, const char *msgbuf)
/* skip five bytes */
index = 5;
- ei_decode_version(header,&index,&version);
- ei_decode_tuple_header(header,&index,&arity);
- ei_decode_long(header,&index,&msg.msgtype);
+ if (ei_decode_version(header,&index,&version)
+ || ei_decode_tuple_header(header,&index,&arity)
+ || ei_decode_long(header,&index,&msg.msgtype)) return -1;
switch (msg.msgtype) {
case ERL_SEND:
diff --git a/lib/erl_interface/src/prog/erl_call.c b/lib/erl_interface/src/prog/erl_call.c
index b0566a0db7..1c9bd69a96 100644
--- a/lib/erl_interface/src/prog/erl_call.c
+++ b/lib/erl_interface/src/prog/erl_call.c
@@ -80,6 +80,7 @@ struct call_flags {
int randomp;
int dynamic_name;
int use_long_name; /* indicates if -name was used, else -sname or -n */
+ int use_localhost_fallback;
int debugp;
int verbosep;
int haltp;
@@ -105,6 +106,23 @@ static void* ei_chk_calloc(size_t nmemb, size_t size);
static void* ei_chk_realloc(void *old, size_t size);
static char* ei_chk_strdup(char *s);
+/* Converts the given hostname to a shortname, if required. */
+static void format_node_hostname(const struct call_flags *flags,
+ const char *hostname,
+ char dst[EI_MAXHOSTNAMELEN + 1])
+{
+ char *ct;
+
+ strncpy(dst, hostname, EI_MAXHOSTNAMELEN);
+ dst[EI_MAXHOSTNAMELEN] = '\0';
+
+ /* If shortnames, cut off the name at first '.' */
+ if (flags->use_long_name == 0 && (ct = strchr(dst, '.'))) {
+ *ct = '\0';
+ }
+}
+
+static void start_timeout(int timeout);
/***************************************************************************
*
@@ -119,7 +137,6 @@ int main(int argc, char *argv[])
char host_name[EI_MAXHOSTNAMELEN+1];
char nodename[MAXNODELEN+1];
char *p = NULL;
- char *ct = NULL; /* temporary used when truncating nodename */
int modsize = 0;
char *host = NULL;
char *module = NULL;
@@ -177,6 +194,24 @@ int main(int argc, char *argv[])
}
i++;
}
+ } else if (strcmp(argv[i], "-timeout") == 0) {
+ long timeout;
+
+ if (i+1 >= argc) {
+ usage_arg(progname, "-timeout ");
+ }
+
+ timeout = strtol(argv[i+1], NULL, 10);
+ if (timeout <= 0 || timeout >= (1 << 20)) {
+ usage_error(progname, "-timeout");
+ }
+
+ start_timeout(timeout);
+ i++;
+ } else if (strcmp(argv[i], "-__uh_test__") == 0) {
+ /* Fakes a failure in the call to ei_gethostbyname(h_hostname) so
+ * we can test the localhost fallback. */
+ flags.use_localhost_fallback = 1;
} else {
if (strlen(argv[i]) != 2) {
usage_error(progname, argv[i]);
@@ -312,23 +347,24 @@ int main(int argc, char *argv[])
char *h_nodename = h_nodename_buf;
char *h_alivename = flags.hidden;
struct in_addr h_ipadr;
- char* ct;
/* gethostname requires len to be max(hostname) + 1 */
if (gethostname(h_hostname, EI_MAXHOSTNAMELEN+1) < 0) {
- fprintf(stderr,"erl_call: failed to get host name: %d\n", errno);
- exit(1);
+ fprintf(stderr,"erl_call: failed to get host name: %d\n", errno);
+ exit(1);
}
- if ((hp = ei_gethostbyname(h_hostname)) == 0) {
- fprintf(stderr,"erl_call: can't resolve hostname %s\n", h_hostname);
- exit(1);
- }
- /* If shortnames, cut off the name at first '.' */
- if (flags.use_long_name == 0 && (ct = strchr(hp->h_name, '.')) != NULL) {
- *ct = '\0';
+
+ if (flags.use_localhost_fallback || (hp = ei_gethostbyname(h_hostname)) == 0) {
+ /* Failed to resolve our own hostname; try binding to loopback and
+ * hope for the best. */
+ hp = ei_gethostbyname("localhost");
+ flags.use_localhost_fallback = 1;
+
+ format_node_hostname(&flags, h_hostname, h_hostname);
+ } else {
+ format_node_hostname(&flags, hp->h_name, h_hostname);
}
- strncpy(h_hostname, hp->h_name, EI_MAXHOSTNAMELEN);
- h_hostname[EI_MAXHOSTNAMELEN] = '\0';
+
memcpy(&h_ipadr.s_addr, *hp->h_addr_list, sizeof(struct in_addr));
if (h_alivename) {
if (strlen(h_alivename) + strlen(h_hostname) + 2 > sizeof(h_nodename_buf)) {
@@ -364,20 +400,21 @@ int main(int argc, char *argv[])
host = p+1;
}
- /*
- * Expand name to a real name (may be ip-address)
- */
- /* FIXME better error string */
- if ((hp = ei_gethostbyname(host)) == 0) {
- fprintf(stderr,"erl_call: can't ei_gethostbyname(%s)\n", host);
- exit(1);
- }
- /* If shortnames, cut off the name at first '.' */
- if (flags.use_long_name == 0 && (ct = strchr(hp->h_name, '.')) != NULL) {
- *ct = '\0';
+ if (flags.use_localhost_fallback && strcmp(host, ei_thishostname(&ec)) == 0) {
+ /* We're on the same host *and* have used the localhost fallback, so we
+ * skip canonical name resolution since it's bound to fail.
+ *
+ * `ei_connect` will do the right thing later on. */
+ strcpy(host_name, ei_thishostname(&ec));
+ } else {
+ if ((hp = ei_gethostbyname(host)) == 0) {
+ fprintf(stderr,"erl_call: can't ei_gethostbyname(%s)\n", host);
+ exit(1);
+ }
+
+ format_node_hostname(&flags, hp->h_name, host_name);
}
- strncpy(host_name, hp->h_name, EI_MAXHOSTNAMELEN);
- host_name[EI_MAXHOSTNAMELEN] = '\0';
+
if (flags.port == -1) {
if (strlen(flags.node) + strlen(host_name) + 2 > sizeof(nodename)) {
fprintf(stderr,"erl_call: nodename too long: %s\n", flags.node);
@@ -832,6 +869,25 @@ static int get_module(char **mbuf, char **mname)
} /* get_module */
+#ifdef __WIN32__
+static DWORD WINAPI timer_thread(void *data) {
+ DWORD_PTR timeout = (DWORD_PTR)data * 1000;
+
+ Sleep(timeout);
+ exit(1);
+}
+
+static void start_timeout(int timeout) {
+ if (CreateThread(NULL, 0, timer_thread, (void*)timeout, 0, NULL) == NULL) {
+ fprintf(stderr,"erl_call: Failed to start timer thread\n");
+ exit(1);
+ }
+}
+#else
+static void start_timeout(int timeout) {
+ alarm(timeout);
+}
+#endif
/***************************************************************************
*
@@ -841,7 +897,7 @@ static int get_module(char **mbuf, char **mname)
static void usage_noexit(const char *progname) {
fprintf(stderr,"\nUsage: %s [-[demqrsv]] [-c Cookie] [-h HiddenName] \n", progname);
- fprintf(stderr," [-x ErlScript] [-a [Mod [Fun [Args]]]]\n");
+ fprintf(stderr," [-x ErlScript] [-a [Mod [Fun [Args]]]] [-timeout Secs]\n");
fprintf(stderr," (-n Node | -sname Node | -name Node | -address [HOSTNAME:]PORT)\n\n");
#ifdef __WIN32__
fprintf(stderr," where: -a apply(Mod,Fun,Args) (e.g -a \"erlang length [[a,b,c]]\"\n");
@@ -862,6 +918,7 @@ static void usage_noexit(const char *progname) {
" (e.g., %s -address my_host:36303 ...)\n"
" (cannot be combinated with -s, -n, -name and -sname)\n",
progname);
+ fprintf(stderr," -timeout command timeout, in seconds\n");
fprintf(stderr," -q halt the Erlang node (overrides the -s switch)\n");
fprintf(stderr," -r use a random name for the erl_call client node\n");
fprintf(stderr," -s start a new Erlang node if necessary\n");
diff --git a/lib/erl_interface/src/prog/erl_start.c b/lib/erl_interface/src/prog/erl_start.c
index 8eca2dfa10..9c876feb5e 100644
--- a/lib/erl_interface/src/prog/erl_start.c
+++ b/lib/erl_interface/src/prog/erl_start.c
@@ -80,7 +80,7 @@ static struct in_addr *get_addr(const char *hostname, struct in_addr *oaddr);
static int wait_for_erlang(int sockd, int magic, struct timeval *timeout);
#if defined(__WIN32__)
static int unique_id(void);
-static unsigned long spawn_erlang_epmd(ei_cnode *ec,
+static HANDLE spawn_erlang_epmd(ei_cnode *ec,
char *alive,
Erl_IpAddr adr,
int flags,
@@ -116,9 +116,9 @@ int erl_start_sys(ei_cnode *ec, char *alive, Erl_IpAddr adr, int flags,
int sockd = 0;
int one = 1;
#if defined(__WIN32__)
- unsigned long pid = 0;
+ HANDLE pid;
#else
- int pid = 0;
+ int pid;
#endif
int r = 0;
@@ -145,8 +145,8 @@ int erl_start_sys(ei_cnode *ec, char *alive, Erl_IpAddr adr, int flags,
listen(sockd,5);
#if defined(__WIN32__)
- if((pid = spawn_erlang_epmd(ec,alive,adr,flags,erl,args,port,1))
- == 0)
+ pid = spawn_erlang_epmd(ec,alive,adr,flags,erl,args,port,1);
+ if (pid == INVALID_HANDLE_VALUE)
return ERL_SYS_ERROR;
timeout.tv_usec = 0;
timeout.tv_sec = 10; /* ignoring ERL_START_TIME */
@@ -154,7 +154,7 @@ int erl_start_sys(ei_cnode *ec, char *alive, Erl_IpAddr adr, int flags,
== ERL_TIMEOUT) {
/* Well, this is not a nice way to do it, and it does not
always kill the emulator, but the alternatives are few.*/
- TerminateProcess((HANDLE) pid,1);
+ TerminateProcess(pid,1);
}
#else /* Unix */
switch ((pid = fork())) {
@@ -269,7 +269,7 @@ static void free_args(char **args){
/* In NT we cannot fork(), Erlang and Epmd gets
spawned by this function instead. */
-static unsigned long spawn_erlang_epmd(ei_cnode *ec,
+static HANDLE spawn_erlang_epmd(ei_cnode *ec,
char *alive,
Erl_IpAddr adr,
int flags,
@@ -290,18 +290,18 @@ static unsigned long spawn_erlang_epmd(ei_cnode *ec,
struct in_addr myaddr;
struct in_addr *hisaddr = (struct in_addr *)adr;
char iaddrbuf[IP_ADDR_CHARS + 1];
- int ret;
+ HANDLE ret;
if(is_erlang){
get_addr(ei_thishostname(ec), &myaddr);
if((ptr = inet_ntoa(myaddr)) == NULL)
- return 0;
+ return INVALID_HANDLE_VALUE;
else
strcpy(iaddrbuf,ptr);
}
if ((flags & ERL_START_REMOTE) ||
(is_erlang && (hisaddr->s_addr != myaddr.s_addr))) {
- return 0;
+ return INVALID_HANDLE_VALUE;
} else {
num_args = enquote_args(args, &args);
for(cmdlen = i = 0; args[i] != NULL; ++i)
@@ -364,9 +364,9 @@ static unsigned long spawn_erlang_epmd(ei_cnode *ec,
NULL,
&sinfo,
&pinfo))
- ret = 0;
+ ret = INVALID_HANDLE_VALUE;
else
- ret = (unsigned long) pinfo.hProcess;
+ ret = pinfo.hProcess;
free(cmdbuf);
return ret;
}
diff --git a/lib/erl_interface/src/registry/reg_dump.c b/lib/erl_interface/src/registry/reg_dump.c
index a027c4cdf5..f90fd4d4b6 100644
--- a/lib/erl_interface/src/registry/reg_dump.c
+++ b/lib/erl_interface/src/registry/reg_dump.c
@@ -209,11 +209,11 @@ static int mn_send_write(int fd, erlang_pid *mnesia, const char *key, ei_reg_obj
break;
case EI_STR:
if (obj->size > 0) ei_encode_string(msgbuf,&index,obj->val.s);
- else ei_encode_long(msgbuf,&index, (long)NULL); /* just the NULL pointer */
+ else ei_encode_long(msgbuf,&index, 0); /* just the NULL pointer */
break;
case EI_BIN:
if (obj->size > 0) ei_encode_binary(msgbuf,&index,obj->val.p,obj->size);
- else ei_encode_long(msgbuf,&index,(long)(obj->val.p)); /* just the pointer */
+ else ei_encode_long(msgbuf,&index, obj->val.i); /* just the pointer */
break;
default:
if (dbuf) free(dbuf);
@@ -255,7 +255,7 @@ static int mn_get_unlink(int fd)
int ei_reg_dump(int fd, ei_reg *reg, const char *mntab, int flags)
{
ei_hash *tab;
- erlang_pid self;
+ erlang_pid *self;
erlang_pid mnesia;
ei_bucket *b;
ei_reg_obj *obj;
@@ -271,12 +271,10 @@ int ei_reg_dump(int fd, ei_reg *reg, const char *mntab, int flags)
if ((ec = ei_fd_to_cnode(fd)) == NULL) {
return -1;
}
- strcpy(self.node,ei_thisnodename(ec));
- self.num = fd;
- self.serial = 0;
- self.creation = ei_thiscreation(ec);
- if (mn_start_dump(fd,&self,&mnesia,mntab)) return -1;
+ self = ei_self(ec);
+
+ if (mn_start_dump(fd,self,&mnesia,mntab)) return -1;
/* traverse the table, passing objects to mnesia */
for (i=0; i<tab->size; i++) {
@@ -288,13 +286,13 @@ int ei_reg_dump(int fd, ei_reg *reg, const char *mntab, int flags)
if ((flags & EI_FORCE) || (obj->attr & EI_DIRTY)) {
if (obj->attr & EI_DELET) {
if (mn_send_delete(fd,&mnesia,key)) {
- ei_send_exit(fd,&self,&mnesia,"delete failed");
+ ei_send_exit(fd,self,&mnesia,"delete failed");
return -1;
}
}
else {
if (mn_send_write(fd,&mnesia,key,obj)) {
- ei_send_exit(fd,&self,&mnesia,"update failed");
+ ei_send_exit(fd,self,&mnesia,"update failed");
return -1;
}
}
@@ -304,8 +302,8 @@ int ei_reg_dump(int fd, ei_reg *reg, const char *mntab, int flags)
}
/* end the transaction */
- if (mn_send_commit(fd,&mnesia,&self)) {
- ei_send_exit(fd,&self,&mnesia,"commit failed");
+ if (mn_send_commit(fd,&mnesia,self)) {
+ ei_send_exit(fd,self,&mnesia,"commit failed");
return -1;
}
diff --git a/lib/erl_interface/src/registry/reg_restore.c b/lib/erl_interface/src/registry/reg_restore.c
index 75d073303f..030bab19b9 100644
--- a/lib/erl_interface/src/registry/reg_restore.c
+++ b/lib/erl_interface/src/registry/reg_restore.c
@@ -227,7 +227,7 @@ int ei_reg_restore(int fd, ei_reg *reg, const char *mntab)
char *dbuf = NULL;
char *msgbuf = NULL;
char *keybuf = NULL;
- erlang_pid self;
+ erlang_pid *self;
erlang_pid mnesia = {"",0,0,0};
erlang_msg msg;
int index = 0;
@@ -247,20 +247,18 @@ int ei_reg_restore(int fd, ei_reg *reg, const char *mntab)
if ((ec = ei_fd_to_cnode(fd)) == NULL) {
return -1;
}
- strcpy(self.node,ei_thisnodename(ec));
- self.num = fd;
- self.serial = 0;
- self.creation = ei_thiscreation(ec);
+
+ self = ei_self(ec);
- if (mn_start_restore(fd,&self,&mnesia,mntab,&count,&maxkey,&maxobj)) {
+ if (mn_start_restore(fd,self,&mnesia,mntab,&count,&maxkey,&maxobj)) {
/* send exit *only* if we have pid */
- if (mnesia.node[0]) ei_send_exit(fd,&self,&mnesia,"bad response from rpc start");
+ if (mnesia.node[0]) ei_send_exit(fd,self,&mnesia,"bad response from rpc start");
return -1;
}
if (count <= 0) {
- ei_send_exit(fd,&self,&mnesia,"nothing to do");
+ ei_send_exit(fd,self,&mnesia,"nothing to do");
return 0;
}
@@ -268,7 +266,7 @@ int ei_reg_restore(int fd, ei_reg *reg, const char *mntab)
len = maxkey + maxobj + 512;
if (len > EISMALLBUF)
if (!(dbuf = malloc(len))) {
- ei_send_exit(fd,&self,&mnesia,"cannot allocate space for incoming data");
+ ei_send_exit(fd,self,&mnesia,"cannot allocate space for incoming data");
return -1;
}
msgbuf = (dbuf ? dbuf : sbuf);
@@ -281,7 +279,7 @@ int ei_reg_restore(int fd, ei_reg *reg, const char *mntab)
ei_encode_version(msgbuf,&index);
ei_encode_tuple_header(msgbuf,&index,2);
ei_encode_atom(msgbuf,&index,"send_records");
- ei_encode_pid(msgbuf,&index,&self);
+ ei_encode_pid(msgbuf,&index,self);
if (ei_send_encoded(fd,&mnesia,msgbuf,index)) goto restore_failure;
/* read as much as possible, until count or EXIT */
@@ -317,7 +315,7 @@ int ei_reg_restore(int fd, ei_reg *reg, const char *mntab)
return 0;
restore_failure:
- ei_send_exit(fd,&self,&mnesia,"restore failure");
+ ei_send_exit(fd,self,&mnesia,"restore failure");
if (keybuf) free(keybuf);
if (dbuf) free(dbuf);
return -1;
diff --git a/lib/erl_interface/test/Makefile b/lib/erl_interface/test/Makefile
index 1816c73d4b..bdfedecc66 100644
--- a/lib/erl_interface/test/Makefile
+++ b/lib/erl_interface/test/Makefile
@@ -24,7 +24,7 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk
# Target Specs
# ----------------------------------------------------
-MODULES= \
+EI_MODULES= \
ei_accept_SUITE \
ei_connect_SUITE \
ei_decode_SUITE \
@@ -38,12 +38,16 @@ MODULES= \
port_call_SUITE \
runner
+ERTS_MODULES= erts_test_utils
+
+MODULES=$(EI_MODULES) $(ERTS_MODULES)
+
SPEC_FILES = \
erl_interface.spec erl_interface_smoke.spec
COVER_FILE = erl_interface.cover
-ERL_FILES = $(MODULES:%=%.erl)
+ERL_FILES = $(EI_MODULES:%=%.erl) $(ERTS_MODULES:%=$(ERL_TOP)/erts/emulator/test/%.erl)
# ----------------------------------------------------
# Release directory specification
diff --git a/lib/erl_interface/test/ei_connect_SUITE.erl b/lib/erl_interface/test/ei_connect_SUITE.erl
index 224608ba71..0506359b71 100644
--- a/lib/erl_interface/test/ei_connect_SUITE.erl
+++ b/lib/erl_interface/test/ei_connect_SUITE.erl
@@ -34,7 +34,9 @@
ei_send_funs/1,
ei_threaded_send/1,
ei_set_get_tracelevel/1,
- ei_connect_host_port_test/1]).
+ ei_connect_host_port_test/1,
+ ei_make_ref/1,
+ ei_make_pid/1]).
-import(runner, [get_term/1,send_term/2]).
@@ -54,7 +56,9 @@ groups() ->
ei_send_funs,
ei_set_get_tracelevel,
ei_reg_send,
- ei_rpc],
+ ei_rpc,
+ ei_make_ref,
+ ei_make_pid],
[{default, [], Members},
{ussi, [], Members}].
@@ -207,6 +211,100 @@ ei_connect_host_port_test(Config) when is_list(Config) ->
runner:recv_eot(P),
ok.
+ei_make_ref(Config) when is_list(Config) ->
+ P = runner:start(Config, ?interpret),
+ 0 = ei_connect_init(P, 42, erlang:get_cookie(), 0, get_group(Config)),
+ {ok,Fd} = ei_connect(P, node()),
+
+ %% Call ei_make_ref() enough times for it to
+ %% wrap the first internal integer..
+
+ N = 270,
+ {CNode, Refs} = make_refs(N, undefined, P, Fd, []),
+ io:format("Last Ref ~p~n", [hd(Refs)]),
+
+ io:format("CNode = ~p", [CNode]),
+
+ true = lists:member(CNode, nodes(hidden)),
+
+ %% Ensure that all references are
+ %% unique...
+ RefsLen = N*1000,
+ RefsLen = length(lists:usort(Refs)),
+
+ runner:send_eot(P),
+ runner:recv_eot(P),
+ ok.
+
+make_refs(0, CNode, _P, _Fd, Refs) ->
+ {CNode, Refs};
+make_refs(N, CNode, P, Fd, Refs) ->
+ ok = ei_make_refs(P, Fd, self()),
+ receive
+ {Node, NewRefs} ->
+ NewNode = if CNode == undefined ->
+ Node;
+ true ->
+ CNode = Node
+ end,
+ make_refs(N-1, NewNode, P, Fd,
+ chk_refs(NewRefs, NewNode, Refs))
+ end.
+
+chk_refs([], _CNode, Refs) ->
+ Refs;
+chk_refs([NewRef|NewRefs], CNode, Refs) ->
+ true = is_reference(NewRef),
+ CNode = node(NewRef),
+ chk_refs(NewRefs, CNode, [NewRef|Refs]).
+
+ei_make_pid(Config) when is_list(Config) ->
+ P = runner:start(Config, ?interpret),
+ 0 = ei_connect_init(P, 42, erlang:get_cookie(), 0, get_group(Config)),
+ {ok,Fd} = ei_connect(P, node()),
+
+ %%
+ %% Ensure to wrap all num values...
+ %%
+ N = 200,
+ {CNode, Pids} = make_pids(N, undefined, P, Fd, []),
+ io:format("Last Pid ~p~n", [hd(Pids)]),
+
+ io:format("CNode = ~p", [CNode]),
+
+ true = lists:member(CNode, nodes(hidden)),
+
+ %% Ensure that all pid created by ei_make_pid()
+ %% are unique. Note that ei_self() is passed
+ %% along in each call as well...
+ PidsLen = N*1000 + 1,
+ PidsLen = length(lists:usort(Pids)),
+
+ runner:send_eot(P),
+ runner:recv_eot(P),
+ ok.
+
+make_pids(0, CNode, _P, _Fd, Pids) ->
+ {CNode, Pids};
+make_pids(N, CNode, P, Fd, Pids) ->
+ ok = ei_make_pids(P, Fd, self()),
+ receive
+ {Node, NewPids} ->
+ NewNode = if CNode == undefined ->
+ Node;
+ true ->
+ CNode = Node
+ end,
+ make_pids(N-1, NewNode, P, Fd,
+ chk_pids(NewPids, NewNode, Pids))
+ end.
+
+chk_pids([], _CNode, Pids) ->
+ Pids;
+chk_pids([NewPid|NewPids], CNode, Pids) ->
+ true = is_pid(NewPid),
+ CNode = node(NewPid),
+ chk_pids(NewPids, CNode, [NewPid|Pids]).
%%% Interface functions for ei (erl_interface) functions.
@@ -256,6 +354,14 @@ ei_rpc(P, Fd, To, Func, Msg) ->
send_command(P, ei_rpc, [Fd, To, Func, Msg]),
get_term(P).
+ei_make_refs(P, Fd, To) ->
+ send_command(P, ei_make_refs, [Fd,To]),
+ get_send_result(P).
+
+ei_make_pids(P, Fd, To) ->
+ send_command(P, ei_make_pids, [Fd,To]),
+ get_send_result(P).
+
get_send_result(P) ->
case get_term(P) of
diff --git a/lib/erl_interface/test/ei_connect_SUITE_data/ei_connect_test.c b/lib/erl_interface/test/ei_connect_SUITE_data/ei_connect_test.c
index 1dcd62400a..0aa6879adf 100644
--- a/lib/erl_interface/test/ei_connect_SUITE_data/ei_connect_test.c
+++ b/lib/erl_interface/test/ei_connect_SUITE_data/ei_connect_test.c
@@ -40,6 +40,8 @@ static void cmd_ei_send_funs(char* buf, int len);
static void cmd_ei_reg_send(char* buf, int len);
static void cmd_ei_rpc(char* buf, int len);
static void cmd_ei_set_get_tracelevel(char* buf, int len);
+static void cmd_ei_make_refs(char* buf, int len);
+static void cmd_ei_make_pids(char* buf, int len);
static void send_errno_result(int value);
@@ -60,6 +62,8 @@ static struct {
"ei_rpc", 4, cmd_ei_rpc,
"ei_set_get_tracelevel", 1, cmd_ei_set_get_tracelevel,
"ei_format_pid", 2, cmd_ei_format_pid,
+ "ei_make_refs", 2, cmd_ei_make_refs,
+ "ei_make_pids", 2, cmd_ei_make_pids,
};
@@ -210,6 +214,80 @@ static void cmd_ei_send(char* buf, int len)
ei_x_free(&x);
}
+static void cmd_ei_make_refs(char* buf, int len)
+{
+ int index = 0;
+ long fd;
+ erlang_pid pid;
+ ei_x_buff x;
+ int i;
+ int nref = 1000;
+
+ if (ei_decode_long(buf, &index, &fd) < 0)
+ fail("expected long");
+ if (ei_decode_pid(buf, &index, &pid) < 0)
+ fail("expected pid (node)");
+ if (ei_x_new_with_version(&x) < 0)
+ fail("ei_x_new_with_version");
+ if (ei_x_encode_tuple_header(&x, 2) < 0)
+ fail("ei_x_encode_tuple_header() failed");
+ if (ei_x_encode_atom(&x, ei_thisnodename(&ec)) < 0)
+ fail("ei_x_encode_atom() failed");
+ if (ei_x_encode_list_header(&x, nref) < 0)
+ fail("ei_x_encode_list_header() failed");
+ for (i = 0; i < nref; i++) {
+ erlang_ref ref;
+ if (ei_make_ref(&ec, &ref))
+ fail("ei_make_ref() failed");
+ if (ei_x_encode_ref(&x, &ref))
+ fail("ei_x_encode_ref() failed");
+ }
+ if (ei_x_encode_empty_list(&x) < 0)
+ fail("ei_x_encode_empty_list() failed");
+ send_errno_result(ei_send(fd, &pid, x.buff, x.index));
+ ei_x_free(&x);
+}
+
+static void cmd_ei_make_pids(char* buf, int len)
+{
+ int index = 0;
+ long fd;
+ erlang_pid from_pid;
+ erlang_pid *self;
+ ei_x_buff x;
+ int i;
+ int npid = 1000;
+
+ if (ei_decode_long(buf, &index, &fd) < 0)
+ fail("expected long");
+ if (ei_decode_pid(buf, &index, &from_pid) < 0)
+ fail("expected pid (node)");
+ if (ei_x_new_with_version(&x) < 0)
+ fail("ei_x_new_with_version");
+ if (ei_x_encode_tuple_header(&x, 2) < 0)
+ fail("ei_x_encode_tuple_header() failed");
+ if (ei_x_encode_atom(&x, ei_thisnodename(&ec)) < 0)
+ fail("ei_x_encode_atom() failed");
+ if (ei_x_encode_list_header(&x, 1+npid) < 0)
+ fail("ei_x_encode_list_header() failed");
+ self = ei_self(&ec);
+ if (!self)
+ fail("ei_self() failed");
+ if (ei_x_encode_pid(&x, self))
+ fail("ei_x_encode_pid() failed");
+ for (i = 0; i < npid; i++) {
+ erlang_pid pid;
+ if (ei_make_pid(&ec, &pid))
+ fail("ei_make_pid() failed");
+ if (ei_x_encode_pid(&x, &pid))
+ fail("ei_x_encode_pid() failed");
+ }
+ if (ei_x_encode_empty_list(&x) < 0)
+ fail("ei_x_encode_empty_list() failed");
+ send_errno_result(ei_send(fd, &from_pid, x.buff, x.index));
+ ei_x_free(&x);
+}
+
static void cmd_ei_format_pid(char* buf, int len)
{
int index = 0;
diff --git a/lib/erl_interface/test/ei_decode_SUITE.erl b/lib/erl_interface/test/ei_decode_SUITE.erl
index 475e68ced2..fee99aba7c 100644
--- a/lib/erl_interface/test/ei_decode_SUITE.erl
+++ b/lib/erl_interface/test/ei_decode_SUITE.erl
@@ -33,7 +33,9 @@
test_ei_decode_char/1,
test_ei_decode_nonoptimal/1,
test_ei_decode_misc/1,
- test_ei_decode_utf8_atom/1]).
+ test_ei_decode_utf8_atom/1,
+ test_ei_decode_iodata/1,
+ test_ei_cmp_nc/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
@@ -41,7 +43,8 @@ all() ->
[test_ei_decode_long, test_ei_decode_ulong,
test_ei_decode_longlong, test_ei_decode_ulonglong,
test_ei_decode_char, test_ei_decode_nonoptimal,
- test_ei_decode_misc, test_ei_decode_utf8_atom].
+ test_ei_decode_misc, test_ei_decode_utf8_atom,
+ test_ei_decode_iodata, test_ei_cmp_nc].
init_per_testcase(Case, Config) ->
runner:init_per_testcase(?MODULE, Case, Config).
@@ -215,6 +218,160 @@ test_ei_decode_utf8_atom(Config) ->
%% ######################################################################## %%
+test_ei_decode_iodata(Config) when is_list(Config) ->
+ P = runner:start(Config, ?test_ei_decode_iodata),
+
+ check_decode_iodata(P, [], true),
+ check_decode_iodata(P, $a, false),
+ check_decode_iodata(P, an_atom, false),
+ check_decode_iodata(P, self(), false),
+ check_decode_iodata(P, [$a,$a], true),
+ check_decode_iodata(P, [$a|$a], false),
+ check_decode_iodata(P, [[$a|$a],$a], false),
+ check_decode_iodata(P, "hej", true),
+ check_decode_iodata(P, ["hej", " ", "hopp"], true),
+ check_decode_iodata(P, <<"hopp san sa">>, true),
+ check_decode_iodata(P, [$a | <<"a">>], true),
+ check_decode_iodata(P, [[[["hej"]]], [$ , <<"hopp">>, $ , "san" | <<" sa">>]], true),
+ check_decode_iodata(P, [[[["hej"]]], [$ , <<"hopp">>, 0, "san" | <<" sa">>]], true),
+ check_decode_iodata(P, [[[["hej"]]], [$ , <<"hopp">>, 256, "san" | <<" sa">>]], false),
+ check_decode_iodata(P, [[[["hej"]]], [$ , <<"hopp">>, -2, "san" | <<" sa">>]], false),
+ check_decode_iodata(P, [[[["hej"]]], [$ , <<"hopp">>, $ , san | <<" sa">>]], false),
+ check_decode_iodata(P, [[[["hej"]]], [$ , <<"hopp">>, $ , "san s" | $a], " "], false),
+ check_decode_iodata(P, [[[[[[[]|<<"a">>]]]]]], true),
+ check_decode_iodata(P, [[[[[[[[]]]]]]],[[[],[],[]]]], true),
+
+ send_raw(P, <<"done">>),
+ runner:recv_eot(P),
+ ok.
+
+check_decode_iodata(P, Data, Valid) ->
+ io:format("~n~nChecking: ~p~n", [Data]),
+ Expect = case Valid of
+ true ->
+ io:format("Expecting decode SUCCESS... ", []),
+ iolist_to_binary(Data);
+ false ->
+ io:format("Expecting decode FAILURE... ", []),
+ badarg = try
+ iolist_to_binary(Data)
+ catch
+ error:badarg ->
+ badarg
+ end,
+ decode_size_failed
+ end,
+ send_term_as_binary(P, Data),
+ Actual = case runner:get_term(P) of
+ {bytes, B} when is_binary(B) ->
+ B;
+ {bytes, L} when is_list(L) ->
+ list_to_binary(L);
+ {term, T} ->
+ T
+ end,
+ case Expect =:= Actual of
+ true ->
+ io:format("Expected result!~n",[]),
+ ok;
+ false ->
+ io:format("Expect: ~w~nActual: ~w~n", [Expect, Actual]),
+ ct:fail(unexpected_result)
+ end.
+
+%% ######################################################################## %%
+
+%% Should be moved to its own suite...
+
+test_ei_cmp_nc(Config) when is_list(Config) ->
+ P = runner:start(Config, ?test_ei_cmp_nc),
+ R0 = make_ref(),
+ R1 = make_ref(),
+ check_cmp(P, R0, R0),
+ check_cmp(P, R0, R1),
+ check_cmp(P, R1, R0),
+ check_cmp(P, mk_ref({a@b, 4711}, [17, 17, 17]), mk_ref({a@c, 4711}, [17, 17, 17])),
+ check_cmp(P, mk_ref({a@b, 4711}, [17, 17, 17]), mk_ref({a@d, 4711}, [17, 17, 17])),
+ check_cmp(P, mk_ref({a@b, 4711}, [17, 17, 17]), mk_ref({a@bc, 4711}, [17, 17, 17])),
+ check_cmp(P, mk_ref({a@b, 4712}, [17, 17, 17]), mk_ref({a@b, 4711}, [17, 17, 17])),
+ check_cmp(P, mk_ref({a@b, 4711}, [17, 17, 17]), mk_ref({a@b, 4711}, [18, 17, 17])),
+ check_cmp(P, mk_ref({a@b, 4711}, [17, 17, 17]), mk_ref({a@b, 4711}, [17, 18, 17])),
+ check_cmp(P, mk_ref({a@b, 4711}, [17, 17, 17]), mk_ref({a@b, 4711}, [17, 17, 18])),
+ check_cmp(P, mk_ref({a@b, 4711}, [0, 17]), mk_ref({a@b, 4711}, [18])),
+ check_cmp(P, mk_ref({a@b, 4711}, [17, 17]), mk_ref({a@b, 4711}, [17, 18])),
+ check_cmp(P, mk_ref({a@b, 4711}, [0, 0, 0]), mk_ref({a@b, 4711}, [17])),
+ check_cmp(P, mk_ref({a@b, 4711}, [0, 0, 17]), mk_ref({a@b, 4711}, [18, 17])),
+ check_cmp(P, mk_ref({a@b, 4711}, [0, 17, 17]), mk_ref({a@b, 4711}, [18])),
+
+ check_cmp(P, self(), self()),
+ check_cmp(P, self(), whereis(file_server_2)),
+ check_cmp(P, whereis(file_server_2), self()),
+ check_cmp(P, mk_pid({a@b, 4711}, 17, 17), mk_pid({a@c, 4711}, 17, 17)),
+ check_cmp(P, mk_pid({a@b, 4711}, 17, 17), mk_pid({a@d, 4711}, 17, 17)),
+ check_cmp(P, mk_pid({a@b, 4711}, 17, 17), mk_pid({a@bc, 4711}, 17, 17)),
+ check_cmp(P, mk_pid({a@b, 4712}, 17, 17), mk_pid({a@b, 4711}, 17, 17)),
+ check_cmp(P, mk_pid({a@b, 4711}, 17, 17), mk_pid({a@b, 4711}, 18, 17)),
+ check_cmp(P, mk_pid({a@b, 4711}, 17, 17), mk_pid({a@b, 4711}, 17, 18)),
+
+ Prt0 = open_port({spawn, "true"},[]),
+ Prt1 = open_port({spawn, "true"},[]),
+
+ check_cmp(P, Prt0, Prt0),
+ check_cmp(P, Prt1, Prt0),
+ check_cmp(P, Prt0, Prt1),
+ check_cmp(P, mk_port({a@b, 4711}, 17), mk_port({a@b, 4711}, 17)),
+ check_cmp(P, mk_port({a@b, 4711}, 17), mk_port({a@d, 4711}, 17)),
+ check_cmp(P, mk_port({a@b, 4711}, 17), mk_port({a@bc, 4711}, 17)),
+ check_cmp(P, mk_port({a@b, 4712}, 17), mk_port({a@b, 4711}, 17)),
+ check_cmp(P, mk_port({a@b, 4711}, 17), mk_port({a@b, 4711}, 18)),
+
+ send_raw(P, <<"done">>),
+ runner:recv_eot(P),
+ ok.
+
+mk_pid(Node, Num, Ser) ->
+ erts_test_utils:mk_ext_pid(Node, Num, Ser).
+
+mk_port(Node, Id) ->
+ erts_test_utils:mk_ext_port(Node, Id).
+
+mk_ref(Node, Numbers) ->
+ erts_test_utils:mk_ext_ref(Node, Numbers).
+
+check_cmp(P, A, B) when is_pid(A), is_pid(B) ->
+ check_cmp(P, {cmp_pids, A, B});
+check_cmp(P, A, B) when is_port(A), is_port(B) ->
+ check_cmp(P, {cmp_ports, A, B});
+check_cmp(P, A, B) when is_reference(A), is_reference(B) ->
+ check_cmp(P, {cmp_refs, A, B}).
+
+check_cmp(P, {_, A, B} = Data) ->
+ io:format("~n~nChecking: ~p~n", [Data]),
+ send_term_as_binary(P, Data),
+ {term, Res} = runner:get_term(P),
+ io:format("Res = ~p~n", [Res]),
+ case {{ei_cmp, Res}, {erlang, cmp_nc(A, B)}} of
+ {{ei_cmp, 0}, {erlang, equal}} ->
+ ok;
+ {{ei_cmp, Cmp}, {erlang, less_than}} when is_integer(Cmp),
+ Cmp < 0 ->
+ ok;
+ {{ei_cmp, Cmp}, {erlang, larger_than}} when is_integer(Cmp),
+ Cmp > 0 -> ok;
+ Fail ->
+ ct:fail(Fail)
+ end.
+
+cmp_nc(A, A) ->
+ equal;
+cmp_nc(A, B) when A < B ->
+ less_than;
+cmp_nc(_, _) ->
+ larger_than.
+
+
+%% ######################################################################## %%
+
send_term_as_binary(Port, Term) when is_port(Port) ->
Port ! {self(), {command, term_to_binary(Term)}}.
diff --git a/lib/erl_interface/test/ei_decode_SUITE_data/ei_decode_test.c b/lib/erl_interface/test/ei_decode_SUITE_data/ei_decode_test.c
index 29542b1c6c..9eed34b0dd 100644
--- a/lib/erl_interface/test/ei_decode_SUITE_data/ei_decode_test.c
+++ b/lib/erl_interface/test/ei_decode_SUITE_data/ei_decode_test.c
@@ -19,6 +19,7 @@
*/
#include <string.h>
+#include <stdlib.h>
#include "ei_runner.h"
@@ -753,6 +754,192 @@ TESTCASE(test_ei_decode_utf8_atom)
/* ******************************************************************** */
+TESTCASE(test_ei_decode_iodata)
+{
+ char *buf = NULL, *data = NULL;
+ ei_init();
+
+ while (1) {
+ int unexpected_write = 0;
+ int i;
+ int len, index, saved_index, err;
+
+ if (buf)
+ free_packet(buf);
+ buf = read_packet(&len);
+
+ if (len == 4
+ && buf[0] == 'd'
+ && buf[1] == 'o'
+ && buf[2] == 'n'
+ && buf[3] == 'e') {
+ break;
+ }
+
+ index = 0;
+ err = ei_decode_version(buf, &index, NULL);
+ if (err != 0) {
+ free_packet(buf);
+ fail1("ei_decode_version returned %d", err);
+ }
+ saved_index = index;
+ err = ei_decode_iodata(buf, &index, &len, NULL);
+ if (err != 0) {
+ ei_x_buff x;
+ ei_x_new_with_version(&x);
+ ei_x_encode_atom(&x, "decode_size_failed");
+ send_bin_term(&x);
+ ei_x_free(&x);
+ continue;
+ }
+ if (data) {
+ data -= 100;
+ free(data);
+ }
+ data = malloc(len + 200);
+ if (!data) {
+ ei_x_buff x;
+ ei_x_new_with_version(&x);
+ ei_x_encode_atom(&x, "malloc_failed");
+ send_bin_term(&x);
+ ei_x_free(&x);
+ continue;
+ }
+ for (i = 0; i < len + 200; i++)
+ data[i] = 'Y';
+ data += 100;
+ err = ei_decode_iodata(buf, &saved_index, NULL, (unsigned char *) data);
+ if (err != 0) {
+ ei_x_buff x;
+ ei_x_new_with_version(&x);
+ ei_x_encode_atom(&x, "decode_data_failed");
+ send_bin_term(&x);
+ ei_x_free(&x);
+ continue;
+ }
+
+ for (i = -100; i < 0; i++) {
+ if (data[i] != 'Y') {
+ ei_x_buff x;
+ ei_x_new_with_version(&x);
+ ei_x_encode_atom(&x, "unexpected_write_before_data");
+ send_bin_term(&x);
+ ei_x_free(&x);
+ unexpected_write = !0;
+ break;
+ }
+ }
+
+ if (!unexpected_write) {
+ for (i = len; i < len + 100; i++) {
+ if (data[i] != 'Y') {
+ ei_x_buff x;
+ ei_x_new_with_version(&x);
+ ei_x_encode_atom(&x, "unexpected_write_after_data");
+ send_bin_term(&x);
+ ei_x_free(&x);
+ unexpected_write = !0;
+ break;
+ }
+ }
+ }
+
+ if (!unexpected_write)
+ send_buffer(data, len);
+ }
+
+ if (buf)
+ free_packet(buf);
+ if (data) {
+ data -= 100;
+ free(data);
+ }
+ report(1);
+}
+
+/* ******************************************************************** */
+
+/*
+ * Does not belong here move to its own suite...
+ */
+TESTCASE(test_ei_cmp_nc)
+{
+ char *buf = NULL;
+ ei_init();
+
+ while (1) {
+ int len, index, arity;
+ char atom[MAXATOMLEN_UTF8];
+ ei_x_buff x;
+
+ if (buf)
+ free_packet(buf);
+ buf = read_packet(&len);
+
+ if (len == 4
+ && buf[0] == 'd'
+ && buf[1] == 'o'
+ && buf[2] == 'n'
+ && buf[3] == 'e') {
+ break;
+ }
+
+ ei_x_new_with_version(&x);
+ index = 0;
+ if (ei_decode_version(buf, &index, NULL)
+ || ei_decode_tuple_header(buf, &index, &arity)
+ || (arity != 3)
+ || ei_decode_atom(buf, &index, atom)) {
+ ei_x_encode_atom(&x, "decode_tuple_failed");
+ }
+ else if (strcmp(atom, "cmp_pids") == 0) {
+ erlang_pid a, b;
+ if (ei_decode_pid(buf, &index, &a)
+ || ei_decode_pid(buf, &index, &b)) {
+ ei_x_encode_atom(&x, "decode_pids_failed");
+ }
+ else {
+ long res = (long) ei_cmp_pids(&a, &b);
+ ei_x_encode_long(&x, res);
+ }
+ }
+ else if (strcmp(atom, "cmp_ports") == 0) {
+ erlang_port a, b;
+ if (ei_decode_port(buf, &index, &a)
+ || ei_decode_port(buf, &index, &b)) {
+ ei_x_encode_atom(&x, "decode_ports_failed");
+ }
+ else {
+ long res = (long) ei_cmp_ports(&a, &b);
+ ei_x_encode_long(&x, res);
+ }
+ }
+ else if (strcmp(atom, "cmp_refs") == 0) {
+ erlang_ref a, b;
+ if (ei_decode_ref(buf, &index, &a)
+ || ei_decode_ref(buf, &index, &b)) {
+ ei_x_encode_atom(&x, "decode_refs_failed");
+ }
+ else {
+ long res = (long) ei_cmp_refs(&a, &b);
+ ei_x_encode_long(&x, res);
+ }
+ }
+ else {
+ ei_x_encode_atom(&x, "unexpected_operation");
+ }
+
+ send_bin_term(&x);
+ ei_x_free(&x);
+ }
+
+ if (buf)
+ free_packet(buf);
+ report(1);
+}
+
+/* ******************************************************************** */
+
int ei_decode_my_atom_as(const char *buf, int *index, char *to,
struct my_atom *atom) {
erlang_char_encoding was,result;
diff --git a/lib/erl_interface/test/ei_global_SUITE_data/ei_global_test.c b/lib/erl_interface/test/ei_global_SUITE_data/ei_global_test.c
index 4c018667fe..dd41afe77e 100644
--- a/lib/erl_interface/test/ei_global_SUITE_data/ei_global_test.c
+++ b/lib/erl_interface/test/ei_global_SUITE_data/ei_global_test.c
@@ -211,6 +211,7 @@ cmd_ei_global_names(char* buf, int len)
send_bin_term(&x);
ei_x_free(&x);
}
+ free(names);
}
static void
diff --git a/lib/erl_interface/test/erl_call_SUITE.erl b/lib/erl_interface/test/erl_call_SUITE.erl
index a35e027cd7..339131fed6 100644
--- a/lib/erl_interface/test/erl_call_SUITE.erl
+++ b/lib/erl_interface/test/erl_call_SUITE.erl
@@ -25,12 +25,16 @@
-export([all/0, smoke/1,
random_cnode_name/1,
- test_connect_to_host_port/1]).
+ test_connect_to_host_port/1,
+ unresolvable_hostname/1,
+ timeout/1]).
all() ->
[smoke,
random_cnode_name,
- test_connect_to_host_port].
+ test_connect_to_host_port,
+ unresolvable_hostname,
+ timeout].
smoke(Config) when is_list(Config) ->
Name = atom_to_list(?MODULE)
@@ -115,6 +119,40 @@ test_connect_to_host_port_do(Name) ->
end,
ok.
+%% OTP-16604: Tests that erl_call works even when the local hostname cannot be
+%% resolved.
+unresolvable_hostname(_Config) ->
+ Name = atom_to_list(?MODULE)
+ ++ "-"
+ ++ integer_to_list(erlang:system_time(microsecond)),
+ Opt = "-__uh_test__",
+
+ try
+ CNodeName = start_node_and_get_c_node_name(Name, [Opt]),
+ [_, Hostname] = string:lexemes(atom_to_list(node()), "@"),
+ DefaultName = list_to_atom("c17@" ++ Hostname),
+ check_eq(CNodeName, DefaultName)
+ after
+ halt_node(Name)
+ end,
+
+ ok.
+
+%% OTP-16604: Test the -timeout option
+timeout(_Config) ->
+ Name = atom_to_list(?MODULE)
+ ++ "-"
+ ++ integer_to_list(erlang:system_time(microsecond)),
+ Opts = ["-timeout", "3"],
+
+ try
+ [] = start_node_and_apply(Name, "timer sleep [10000]", Opts)
+ after
+ halt_node(Name)
+ end,
+
+ ok.
+
%
% Utility functions...
%
diff --git a/lib/erl_interface/vsn.mk b/lib/erl_interface/vsn.mk
index 31e3d1c0a0..67aac42e4e 100644
--- a/lib/erl_interface/vsn.mk
+++ b/lib/erl_interface/vsn.mk
@@ -1,2 +1,2 @@
-EI_VSN = 3.13.2
+EI_VSN = 4.0
ERL_INTERFACE_VSN = $(EI_VSN)
diff --git a/lib/eunit/doc/src/notes.xml b/lib/eunit/doc/src/notes.xml
index 200ffc3714..b015fe1f89 100644
--- a/lib/eunit/doc/src/notes.xml
+++ b/lib/eunit/doc/src/notes.xml
@@ -33,6 +33,35 @@
</header>
<p>This document describes the changes made to the EUnit application.</p>
+<section><title>Eunit 2.5</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p> Let <c>eunit_surefire</c> skip invalid XML 1.0
+ characters. </p>
+ <p>
+ Own Id: OTP-15950 Aux Id: PR-2316, ERL-991 </p>
+ </item>
+ <item>
+ <p>
+ Add new macro ?capturedOutput for enabling to write test
+ cases that verify data printed to standard out</p>
+ <p>
+ Own Id: OTP-16275 Aux Id: PR-2424 </p>
+ </item>
+ <item>
+ <p>
+ Add option to limit print depth of exceptions generated
+ by eunit test suites.</p>
+ <p>
+ Own Id: OTP-16549 Aux Id: PR-2532 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Eunit 2.4.1</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/eunit/vsn.mk b/lib/eunit/vsn.mk
index f96db657cf..b8410e4071 100644
--- a/lib/eunit/vsn.mk
+++ b/lib/eunit/vsn.mk
@@ -1 +1 @@
-EUNIT_VSN = 2.4.1
+EUNIT_VSN = 2.5
diff --git a/lib/hipe/doc/src/notes.xml b/lib/hipe/doc/src/notes.xml
index f13551db83..1f9ab57727 100644
--- a/lib/hipe/doc/src/notes.xml
+++ b/lib/hipe/doc/src/notes.xml
@@ -31,6 +31,38 @@
</header>
<p>This document describes the changes made to HiPE.</p>
+<section><title>Hipe 4.0</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Fixed a rare miss-compilation of tuple matching.</p>
+ <p>
+ Own Id: OTP-16470</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>The deprecated <c>erlang:get_stacktrace/0</c> BIF now
+ returns an empty list instead of a stacktrace. To
+ retrieve the stacktrace, use the extended try/catch
+ syntax that was introduced in OTP 21.
+ <c>erlang:get_stacktrace/0</c> is scheduled for removal
+ in OTP 24.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16484</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Hipe 3.19.3</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/hipe/vsn.mk b/lib/hipe/vsn.mk
index 5d34c61169..9a612e9063 100644
--- a/lib/hipe/vsn.mk
+++ b/lib/hipe/vsn.mk
@@ -1 +1 @@
-HIPE_VSN = 3.19.3
+HIPE_VSN = 4.0
diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml
index fe6d7c8c83..0dfd01eab3 100644
--- a/lib/inets/doc/src/notes.xml
+++ b/lib/inets/doc/src/notes.xml
@@ -33,7 +33,31 @@
<file>notes.xml</file>
</header>
- <section><title>Inets 7.1.3</title>
+ <section><title>Inets 7.2</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Remove support for deprecated functionality. Support for
+ mod_esi eval scheme, mod_htacess, mod_browser, apache
+ config files and deprecated httpd_conf functions are
+ dropped. Module http_uri is deprecated.</p>
+ <p>
+ Own Id: OTP-16252</p>
+ </item>
+ <item>
+ <p>Refactored the internal handling of deprecated and
+ removed functions.</p>
+ <p>
+ Own Id: OTP-16469</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Inets 7.1.3</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk
index b0ae8655d2..2eaef40a7a 100644
--- a/lib/inets/vsn.mk
+++ b/lib/inets/vsn.mk
@@ -19,6 +19,6 @@
# %CopyrightEnd%
APPLICATION = inets
-INETS_VSN = 7.1.3
+INETS_VSN = 7.2
PRE_VSN =
APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)"
diff --git a/lib/jinterface/doc/src/notes.xml b/lib/jinterface/doc/src/notes.xml
index 9eeca8e963..fca0b46878 100644
--- a/lib/jinterface/doc/src/notes.xml
+++ b/lib/jinterface/doc/src/notes.xml
@@ -31,6 +31,35 @@
</header>
<p>This document describes the changes made to the Jinterface application.</p>
+<section><title>Jinterface 1.11</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Increased size of node incarnation numbers (aka
+ "creation"), from 2 bits to 32 bits. This will reduce the
+ risk of pids/ports/refs, from different node incarnation
+ with the same name, being mixed up.</p>
+ <p>
+ Own Id: OTP-15603</p>
+ </item>
+ <item>
+ <p>
+ Improved node connection setup handshake protocol. Made
+ possible to agree on protocol version without dependence
+ on <c>epmd</c> or other prior knowledge of peer node
+ version. Also added exchange of node incarnation
+ ("creation") values and expanded the distribution
+ capability flag field from 32 to 64 bits.</p>
+ <p>
+ Own Id: OTP-16229</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Jinterface 1.10.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/jinterface/vsn.mk b/lib/jinterface/vsn.mk
index f15a3f323b..f253cf7431 100644
--- a/lib/jinterface/vsn.mk
+++ b/lib/jinterface/vsn.mk
@@ -1 +1 @@
-JINTERFACE_VSN = 1.10.1
+JINTERFACE_VSN = 1.11
diff --git a/lib/kernel/doc/src/Makefile b/lib/kernel/doc/src/Makefile
index 9b004b3781..d02e954ba9 100644
--- a/lib/kernel/doc/src/Makefile
+++ b/lib/kernel/doc/src/Makefile
@@ -67,9 +67,11 @@ XML_REF3_FILES = application.xml \
pg2.xml \
rpc.xml \
seq_trace.xml \
+ socket.xml \
wrap_log_reader.xml \
user.xml \
- zlib_stub.xml
+ zlib_stub.xml \
+ $(XML_REF3_ESOCK_EFILES)
XML_REF4_FILES = app.xml config.xml
@@ -79,8 +81,10 @@ XML_PART_FILES = part.xml
XML_CHAPTER_FILES = \
notes.xml \
introduction_chapter.xml \
+ socket_usage.xml \
logger_chapter.xml \
- logger_cookbook.xml
+ logger_cookbook.xml \
+ eep48_chapter.xml
BOOK_FILES = book.xml
diff --git a/lib/kernel/doc/src/code.xml b/lib/kernel/doc/src/code.xml
index 421c1ca254..81186b6876 100644
--- a/lib/kernel/doc/src/code.xml
+++ b/lib/kernel/doc/src/code.xml
@@ -705,7 +705,7 @@ ok = code:finish_loading(Prepared),
</desc>
</func>
<func>
- <name name="all_available" arity="0" since="OTP @OTP-16494@"/>
+ <name name="all_available" arity="0" since="OTP 23.0"/>
<fsummary>Get all available modules.</fsummary>
<type name="loaded_filename"/>
<type name="loaded_ret_atoms"/>
@@ -769,14 +769,13 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
- <name name="get_doc" arity="1" since="OTP @OTP-16406@"/>
+ <name name="get_doc" arity="1" since="OTP 23.0"/>
<fsummary>Gets the documentation for a module.</fsummary>
<desc>
- <p>Searches the code path for a documentation chunk
- and returns ut if available. If no documentation chunk
- can be found the function tries to generate documentation
- from the debug information in the module. If no debug
- information is available, this function will return
+ <p>Searches the code path for EEP-48 style documentation and returns it
+ if available. If no documentation can be found the function tries to
+ generate documentation from the debug information in the module.
+ If no debug information is available, this function will return
<c>{error,missing}</c>.
</p>
<p>For more information about the documentation chunk see
diff --git a/lib/kernel/doc/src/eep48_chapter.xml b/lib/kernel/doc/src/eep48_chapter.xml
new file mode 100644
index 0000000000..db5d13eb7a
--- /dev/null
+++ b/lib/kernel/doc/src/eep48_chapter.xml
@@ -0,0 +1,188 @@
+<?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>EEP-48: Documentation storage and format</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ <file>eep48_chapter.xml</file>
+ </header>
+
+ <p>This User's Guide describes the documentation storage format initially described in
+ <url href="https://www.erlang.org/erlang-enhancement-proposals/eep-0048.html">EEP-48</url>.
+ By standardizing how API documentation is stored, it will be possible to write tools that
+ work across languages.</p>
+
+ <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>
+
+ <section>
+ <title>the "Docs" storage</title>
+ <p>To look for documentation for a module name example, a tool should:</p>
+
+ <p>Look for <c>example.beam</c> in the code path, parse the BEAM file and
+ retrieve the <c>Docs</c> chunk. If the chunk is not available, it should look
+ for "example.beam" in the code path and find the <c>doc/chunks/example.chunk</c>
+ file in the application that defines the <c>example</c> module. If a .chunk file is not
+ available, then documentation is not available.</p>
+
+ <p>The choice of using a chunk or the filesystem is completely up to the language or library.
+ In both cases, the documentation can be added or removed at any moment by stripping
+ the <c>Docs</c> chunk or by removing the doc/chunks directory.</p>
+
+ <p>For example, languages like Elixir and LFE attach the <c>Docs</c> chunk at
+ compilation time, which can be controlled via a compiler flag. On the other hand,
+ projects like OTP itself will likely generate the doc/chunks entries on a separate
+ command, completely unrelated from code compilation.</p>
+ </section>
+
+ <section>
+ <title>the "Docs" format</title>
+ <p>In both storages, the documentation is written in the exactly same format:
+ an Erlang term serialized to binary via <seemfa marker="erts:erlang#term_to_binary/1">
+ <c>term_to_binary/1</c></seemfa>. The term may be optionally compressed when serialized.
+ It must follow the type specification below:</p>
+
+ <code>
+{docs_v1,
+ Anno :: erl_anno:anno(),
+ BeamLanguage :: atom(),
+ Format :: binary(),
+ ModuleDoc :: #{DocLanguage := DocValue} | none | hidden,
+ Metadata :: map(),
+ Docs ::
+ [{{Kind, Name, Arity},
+ Anno :: erl_anno:anno(),
+ Signature :: [binary()],
+ Doc :: #{DocLanguage := DocValue} | none | hidden,
+ Metadata :: map()
+ }]} when DocLanguage :: binary(),
+ DocValue :: binary() | term()
+ </code>
+ <p>where in the root tuple we have:</p>
+ <taglist>
+ <tag>Anno</tag>
+ <item>annotation (line, column, file) of the definition itself (see
+ <seeerl marker="stdlib:erl_anno"><c>erl_anno(3)</c></seeerl>)</item>
+
+ <tag>BeamLanguage</tag>
+ <item>an atom representing the language, for example: erlang, elixir, lfe, alpaca, etc</item>
+
+ <tag>Format</tag>
+ <item>the mime type of the documentation, such as &lt;&lt;"text/markdown"&gt;&gt; or
+ &lt;&lt;"application/erlang+html"&gt;&gt;. For details of the format used by Erlang
+ see the <seeguide marker="erl_docgen:doc_storage"><c>EEP-48 Chapter</c></seeguide>
+ in Erl_Docgen's User's Guide.</item>
+
+ <tag>ModuleDoc</tag>
+ <item>a map with the documentation language as key, such as <c>&lt;&lt;"en"&gt;&gt;</c> or
+ <c>&lt;&lt;"pt_BR"&gt;&gt;</c>, and the documentation as a binary value. It may be the
+ atom <c>none</c> in case there is no documentation or the atom <c>hidden</c> if documentation
+ has been explicitly disabled for this entry.</item>
+
+ <tag>Metadata</tag>
+ <item>a map of atom keys with any term as value. This can be used to add annotations like the
+ <c>authors</c> of a module, <c>deprecated</c>, or anything else a language or documentation
+ tool may find relevant.</item>
+
+ <tag>Docs</tag>
+ <item>a list of documentation for other entities (such as functions and types)
+ in the module.</item>
+ </taglist>
+
+ <p>For each entry in Docs, we have:</p>
+
+ <taglist>
+ <tag>{Kind, Name, Arity}</tag>
+ <item>the kind, name and arity identifying the function, callback, type, etc.
+ The official entities are: <c>function</c>, <c>type</c> and <c>callback</c>.
+ Other languages will add their own. For instance, Elixir and LFE may add macro.</item>
+
+ <tag>Anno</tag>
+ <item>annotation (line, column, file) of the module documentation or of the definition itself
+ (see <seeerl marker="stdlib:erl_anno"><c>erl_anno(3)</c></seeerl>).</item>
+
+ <tag>Signature</tag>
+ <item>the signature of the entity. It is is a list of binaries. Each entry represents a
+ binary in the signature that can be joined with a whitespace or a newline. For example,
+ <c>[&lt;&lt;"binary_to_atom(Binary, Encoding)"&gt;&gt;,
+ &lt;&lt;"when is_binary(Binary)"&gt;&gt;]</c> may be rendered as
+ a single line or two lines. It exists exclusively for exhibition purposes.</item>
+
+ <tag>Doc</tag>
+ <item>a map with the documentation language as key, such as &lt;&lt;"en"&gt;&gt; or
+ &lt;&lt;"pt_BR"&gt;&gt;, and the documentation as a value. The documentation may
+ either be a binary or any Erlang term, both described by <c>Format</c>. If it is an
+ Erlang term, then the Format must be &lt;&lt;"application/erlang+SUFFIX",&gt;&gt;
+ such as &lt;&lt;"application/erlang+html"&gt;&gt; when the documentation is an Erlang
+ representation of an HTML document. The Doc may also be atom <c>none</c> in case there is
+ no documentation or the atom <c>hidden</c> if documentation has been explicitly
+ disabled for this entry.</item>
+
+ <tag>Metadata</tag>
+ <item>a map of atom keys with any term as value.</item>
+ </taglist>
+
+ <p>This shared format is the heart of the EEP as it is what effectively
+ allows cross-language collaboration.</p>
+
+ <p>The Metadata field exists to allow languages, tools and libraries to add
+ custom information to each entry. This EEP documents the following metadata keys:</p>
+
+ <taglist>
+ <tag>authors := [binary()]</tag>
+ <item>a list of authors as binaries.</item>
+
+ <tag>cross_references := [module() | {module(), {Kind, Name, Arity}}]</tag>
+ <item>a list of modules or module entries that can be used as cross references
+ when generating documentation.</item>
+
+ <tag>deprecated := binary()</tag>
+ <item>when present, it means the current entry is deprecated with a binary
+ that represents the reason for deprecation and a recommendation to replace
+ the deprecated code.</item>
+
+ <tag>since := binary()</tag>
+ <item>a binary representing the version such entry was added, such
+ as &lt;&lt;"1.3.0"&gt;&gt; or &lt;&lt;"20.0"&gt;&gt;.</item>
+
+ <tag>edit_url := binary()</tag>
+ <item>a binary representing a URL to change to change the documentation itself.</item>
+ </taglist>
+
+ <p>Any key may be added to Metadata at any time. Keys that are frequently
+ used by the community can be standardized in future versions.</p>
+ </section>
+
+ <section>
+ <title>See Also</title>
+ <p>
+ <seeerl marker="stdlib:erl_anno"><c>erl_anno(3)</c></seeerl>,
+ <seeerl marker="stdlib:shell_docs"><c>shell_docs(3)</c></seeerl>,
+ <seeguide marker="erl_docgen:doc_storage"><c>EEP-48 Chapter in Erl_Docgen's User's Guide</c></seeguide>,
+ <seemfa marker="code#get_doc/1"><c>code:get_doc/1</c></seemfa>
+ </p>
+ </section>
+</chapter>
diff --git a/lib/kernel/doc/src/erl_epmd.xml b/lib/kernel/doc/src/erl_epmd.xml
index fbb316bbfc..03aa949516 100644
--- a/lib/kernel/doc/src/erl_epmd.xml
+++ b/lib/kernel/doc/src/erl_epmd.xml
@@ -73,7 +73,7 @@
</func>
<func>
- <name name="listen_port_please" arity="2" since="OTP @OTP-16250@"/>
+ <name name="listen_port_please" arity="2" since="OTP 23.0"/>
<fsummary>Returns the port number for the local node.</fsummary>
<desc>
<p>Called by the distribution module to get which port the
diff --git a/lib/kernel/doc/src/erpc.xml b/lib/kernel/doc/src/erpc.xml
index d609bc3fd9..64032a7f94 100644
--- a/lib/kernel/doc/src/erpc.xml
+++ b/lib/kernel/doc/src/erpc.xml
@@ -28,7 +28,7 @@
<date>2020-02-20</date>
<rev>A</rev>
</header>
- <module since="OTP @OTP-13450@">erpc</module>
+ <module since="OTP 23.0">erpc</module>
<modulesummary>Enhanced Remote Procedure Call</modulesummary>
<description>
<p>
@@ -75,8 +75,8 @@
<funcs>
<func>
- <name name="call" arity="2" since="OTP @OTP-13450@"/>
- <name name="call" arity="3" since="OTP @OTP-13450@"/>
+ <name name="call" arity="2" since="OTP 23.0"/>
+ <name name="call" arity="3" since="OTP 23.0"/>
<fsummary>Evaluate a function call on a node.</fsummary>
<desc>
<p>
@@ -96,8 +96,8 @@
</func>
<func>
- <name name="call" arity="4" since="OTP @OTP-13450@"/>
- <name name="call" arity="5" since="OTP @OTP-13450@"/>
+ <name name="call" arity="4" since="OTP 23.0"/>
+ <name name="call" arity="5" since="OTP 23.0"/>
<fsummary>Evaluate a function call on a node.</fsummary>
<desc>
<p>
@@ -249,7 +249,7 @@
</func>
<func>
- <name name="cast" arity="2" since="OTP @OTP-13450@"/>
+ <name name="cast" arity="2" since="OTP 23.0"/>
<fsummary>Evaluate a function call on a node.</fsummary>
<desc>
<p>
@@ -266,7 +266,7 @@
</func>
<func>
- <name name="cast" arity="4" since="OTP @OTP-13450@"/>
+ <name name="cast" arity="4" since="OTP 23.0"/>
<fsummary>Evaluate a function call on a node ignoring the result.</fsummary>
<desc>
<p>
@@ -298,7 +298,7 @@
</func>
<func>
- <name name="check_response" arity="2" since="OTP @OTP-13450@"/>
+ <name name="check_response" arity="2" since="OTP 23.0"/>
<fsummary>Check if a message is a response corresponding to a
previously sent call request.</fsummary>
<desc>
@@ -341,8 +341,8 @@
</func>
<func>
- <name name="multicall" arity="2" since="OTP @OTP-13450@"/>
- <name name="multicall" arity="3" since="OTP @OTP-13450@"/>
+ <name name="multicall" arity="2" since="OTP 23.0"/>
+ <name name="multicall" arity="3" since="OTP 23.0"/>
<fsummary>Evaluate a function call on a node.</fsummary>
<desc>
<p>
@@ -362,8 +362,8 @@
</func>
<func>
- <name name="multicall" arity="4" since="OTP @OTP-13450@"/>
- <name name="multicall" arity="5" since="OTP @OTP-13450@"/>
+ <name name="multicall" arity="4" since="OTP 23.0"/>
+ <name name="multicall" arity="5" since="OTP 23.0"/>
<fsummary>Evaluate a function call on a number of nodes.</fsummary>
<type name="caught_call_exception"/>
<type name="stack_item"/>
@@ -466,7 +466,7 @@ my_multicall(Nodes, Module, Function, Args) ->
</func>
<func>
- <name name="multicast" arity="2" since="OTP @OTP-13450@"/>
+ <name name="multicast" arity="2" since="OTP 23.0"/>
<fsummary>Evaluate a function call on a set nodes.</fsummary>
<desc>
<p>
@@ -483,7 +483,7 @@ my_multicall(Nodes, Module, Function, Args) ->
</func>
<func>
- <name name="multicast" arity="4" since="OTP @OTP-13450@"/>
+ <name name="multicast" arity="4" since="OTP 23.0"/>
<fsummary>Evaluate a function call on a set of nodes ignoring the result.</fsummary>
<desc>
<p>
@@ -517,8 +517,8 @@ my_multicall(Nodes, Module, Function, Args) ->
</func>
<func>
- <name name="receive_response" arity="1" since="OTP @OTP-13450@"/>
- <name name="receive_response" arity="2" since="OTP @OTP-13450@"/>
+ <name name="receive_response" arity="1" since="OTP 23.0"/>
+ <name name="receive_response" arity="2" since="OTP 23.0"/>
<fsummary>Receive a call response corresponding to a
previously sent call request.</fsummary>
<desc>
@@ -581,7 +581,7 @@ my_call(Node, Module, Function, Args, Timeout) ->
</func>
<func>
- <name name="send_request" arity="2" since="OTP @OTP-13450@"/>
+ <name name="send_request" arity="2" since="OTP 23.0"/>
<fsummary>Send a request to evaluate a function call on a node.</fsummary>
<desc>
<p>
@@ -606,7 +606,7 @@ my_call(Node, Module, Function, Args, Timeout) ->
</func>
<func>
- <name name="send_request" arity="4" since="OTP @OTP-13450@"/>
+ <name name="send_request" arity="4" since="OTP 23.0"/>
<fsummary>Send a request to evaluate a function call on a node.</fsummary>
<desc>
<p>
@@ -633,8 +633,8 @@ my_call(Node, Module, Function, Args, Timeout) ->
</func>
<func>
- <name name="wait_response" arity="1" since="OTP @OTP-13450@"/>
- <name name="wait_response" arity="2" since="OTP @OTP-13450@"/>
+ <name name="wait_response" arity="1" since="OTP 23.0"/>
+ <name name="wait_response" arity="2" since="OTP 23.0"/>
<fsummary>Wait or poll for a call response corresponding to a previously
sent call request.</fsummary>
<desc>
diff --git a/lib/kernel/doc/src/gen_tcp.xml b/lib/kernel/doc/src/gen_tcp.xml
index fdde6e40b4..b90cc9d104 100644
--- a/lib/kernel/doc/src/gen_tcp.xml
+++ b/lib/kernel/doc/src/gen_tcp.xml
@@ -292,7 +292,7 @@ do_recv(Sock, Bs) ->
If any other process is interacting with the socket while
the transfer is happening, the transfer may not work correctly
and messages may remain in the caller's mailbox. For instance
- changing the sockets active mode before the transfere is complete
+ changing the sockets active mode before the transfer is complete
may cause this.</p>
</desc>
</func>
diff --git a/lib/kernel/doc/src/kernel_app.xml b/lib/kernel/doc/src/kernel_app.xml
index cdfafd90eb..bbe4526f0c 100644
--- a/lib/kernel/doc/src/kernel_app.xml
+++ b/lib/kernel/doc/src/kernel_app.xml
@@ -134,7 +134,7 @@
<p>The parameter is described in
<seemfa marker="application#load/2"><c>application:load/2</c></seemfa>.</p>
</item>
- <tag><c>dist_auto_connect = Value</c></tag>
+ <tag><marker id="dist_auto_connect"/><c>dist_auto_connect = Value</c></tag>
<item>
<p>Specifies when nodes are automatically connected. If
this parameter is not specified, a node is always
@@ -152,6 +152,12 @@
<seeerl marker="net_kernel"><c>net_kernel(3)</c></seeerl>.</p></item>
</taglist>
</item>
+ <tag><marker id="dist_listen"/><c>dist_listen = boolean()</c></tag>
+ <item>
+ <p>Specifies whether this node should be listening for incoming
+ distribution connections. Using this option implies that the node
+ also is <seecom marker="erts:erl#hidden"><c>-hidden</c></seecom>.</p>
+ </item>
<tag><c>permissions = [Perm]</c></tag>
<item>
<p>Specifies the default permission for applications when they
diff --git a/lib/kernel/doc/src/notes.xml b/lib/kernel/doc/src/notes.xml
index 624ccb6591..478607d0c8 100644
--- a/lib/kernel/doc/src/notes.xml
+++ b/lib/kernel/doc/src/notes.xml
@@ -31,6 +31,406 @@
</header>
<p>This document describes the changes made to the Kernel application.</p>
+<section><title>Kernel 7.0</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix race condition during shutdown when
+ <c>shell_history</c> is enabled. The race condition would
+ trigger crashes in <c>disk_log</c>.</p>
+ <p>
+ Own Id: OTP-16008 Aux Id: PR-2302 </p>
+ </item>
+ <item>
+ <p>
+ Fix the Erlang distribution to handle the scenario when a
+ node connects that can handle message fragmentation but
+ can not handle the atom cache. This bug only affects
+ users that have implemented a custom distribution
+ carrier. It has been present since OTP-21.</p>
+ <p>
+ The <c>DFLAG_FRAGMENT</c> distribution flag was added to
+ the set of flags that can be rejected by a distribution
+ implementation.</p>
+ <p>
+ Own Id: OTP-16284</p>
+ </item>
+ <item>
+ <p>
+ Fix bug where a binary was not allowed to be the format
+ string in calls to <c>logger:log</c>.</p>
+ <p>
+ Own Id: OTP-16395 Aux Id: PR-2444 </p>
+ </item>
+ <item>
+ <p>
+ Fix bug where <c>logger</c> would end up in an infinite
+ loop when trying to log the crash of a handler or
+ formatter.</p>
+ <p>
+ Own Id: OTP-16489 Aux Id: ERL-1134 </p>
+ </item>
+ <item>
+ <p>
+ <c>code:lib_dir/1</c> has been fixed to also return the
+ lib dir for <c>erts</c>.</p>
+ <p>
+ This is been marked as an incompatibility for any
+ application that depended on <c>{error,bad_name}</c> to
+ be returned for <c>erts</c>.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16502</p>
+ </item>
+ <item>
+ <p>
+ The application <c>stop/1</c> callback was not called if
+ the application master of the application terminated.</p>
+ <p>
+ Own Id: OTP-16504 Aux Id: PR-2328 </p>
+ </item>
+ <item>
+ <p>
+ Fix bug in <c>application:loaded_applications/0</c> that
+ could cause it to fail with <c>badarg</c> if for example
+ a concurrent upgrade/downgrade is running.</p>
+ <p>
+ Own Id: OTP-16627 Aux Id: PR-2601 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>A new module <seeerl
+ marker="kernel:erpc"><c>erpc</c></seeerl> has been
+ introduced in the <c>kernel</c> application. The
+ <c>erpc</c> module implements an enhanced subset of the
+ operations provided by the <seeerl
+ marker="kernel:rpc"><c>rpc</c></seeerl> module. Enhanced
+ in the sense that it makes it possible to distinguish
+ between returned value, raised exceptions, and other
+ errors. <c>erpc</c> also has better performance and
+ scalability than the original <c>rpc</c> implementation.
+ This by utilizing the newly introduced <seemfa
+ marker="erts:erlang#spawn_request/5"><c>spawn_request()</c></seemfa>
+ BIF. Also the <c>rpc</c> module benefits from these
+ improvements by utilizing <c>erpc</c> when it is
+ possible. </p><p> This change has been marked as a
+ potential incompatibility since <seemfa
+ marker="kernel:rpc#block_call/5"><c>rpc:block_call()</c></seemfa>
+ now only is guaranteed to block other <c>block_call()</c>
+ operations. The documentation previously claimed that it
+ would block all <c>rpc</c> operations. This has however
+ never been the case. It previously did not block
+ node-local <c>block_call()</c> operations.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-13450 Aux Id: OTP-15251 </p>
+ </item>
+ <item>
+ <p>A client node can receive its node name dynamically
+ from the node that it first connects to. This featured
+ can by used by</p> <list> <item><p>starting with <c>erl
+ -sname undefined</c></p></item> <item><p>erl_interface
+ functions <c>ei_connect_init</c> and friends</p></item>
+ <item><p><c>erl_call -R</c></p></item> </list>
+ <p>
+ Own Id: OTP-13812</p>
+ </item>
+ <item>
+ <p>
+ Improved the printout of single line logger events for
+ most of the OTP behaviours in STDLIB and Kernel. This
+ includes <c>proc_lib</c>, <c>gen_server</c>,
+ <c>gen_event</c>, <c>gen_statem</c>, <c>gen_fsm</c>,
+ <c>supervisor</c>, <c>supervisor_bridge</c> and
+ <c>application</c>.</p>
+ <p>
+ Improved the <seeerl
+ marker="kernel:logger_formatter#chars_limit"><c>chars_limit</c></seeerl>
+ and <seeerl
+ marker="kernel:logger_formatter#depth"><c>depth</c></seeerl>
+ handling in <c>proc_lib</c> and when formatting of
+ exceptions.</p>
+ <p>
+ Own Id: OTP-15299</p>
+ </item>
+ <item>
+ <p>
+ Remove usage and documentation of old requests of the
+ I/O-protocol.</p>
+ <p>
+ Own Id: OTP-15695</p>
+ </item>
+ <item>
+ <p>Directories can now be opened by <c>file:open/2</c>
+ when passing the <c>directory</c> option.</p>
+ <p>
+ Own Id: OTP-15835 Aux Id: PR-2212 </p>
+ </item>
+ <item>
+ <p>
+ The check of whether to log or not based on the log level
+ in <c>logger</c> has been optimized by using
+ <c>persistent_term</c> to store the log level.</p>
+ <p>
+ Own Id: OTP-15948 Aux Id: PR-2356 </p>
+ </item>
+ <item>
+ <p><c>file:read_file_info/2</c> can now be used on opened
+ files and directories.</p>
+ <p>
+ Own Id: OTP-15956 Aux Id: PR-2231 </p>
+ </item>
+ <item>
+ <p>
+ The <c>-config</c> option to <c>erl</c> now can take
+ multiple config files without repeating the
+ <c>-config</c> option. Example:</p>
+ <p>
+ erl -config sys local</p>
+ <p>
+ Own Id: OTP-16148 Aux Id: PR-2373 </p>
+ </item>
+ <item>
+ <p>
+ Improved node connection setup handshake protocol. Made
+ possible to agree on protocol version without dependence
+ on <c>epmd</c> or other prior knowledge of peer node
+ version. Also added exchange of node incarnation
+ ("creation") values and expanded the distribution
+ capability flag field from 32 to 64 bits.</p>
+ <p>
+ Own Id: OTP-16229</p>
+ </item>
+ <item>
+ <p>The possibility to run Erlang distribution without
+ relying on EPMD has been extended. To achieve this a
+ couple of new options to the inet distribution has been
+ added.</p> <taglist> <tag>-dist_listen false</tag>
+ <item>Setup the distribution channel, but do not listen
+ for incoming connection. This is useful when you want to
+ use the current node to interact with another node on the
+ same machine without it joining the entire
+ cluster.</item> <tag>-erl_epmd_port Port</tag>
+ <item>Configure a default port that the built-in EPMD
+ client should return. This allows the local node to know
+ the port to connect to for any other node in the
+ cluster.</item> </taglist> <p>The <c>erl_epmd</c>
+ callback API has also been extended to allow returning
+ <c>-1</c> as the creation which means that a random
+ creation will be created by the node.</p>
+ <p>In addition a new callback function called
+ <c>listen_port_please</c> has been added that allows the
+ callback to return which listen port the distribution
+ should use. This can be used instead of
+ <c>inet_dist_listen_min/max</c> if the listen port is to
+ be fetched from an external service.</p>
+ <p>
+ Own Id: OTP-16250</p>
+ </item>
+ <item>
+ <p>
+ A first EXPERIMENTAL module that is a <c>socket</c>
+ backend to <c>gen_tcp</c> and <c>inet</c> has been
+ implemented. Others will follow. Feedback will be
+ appreciated.</p>
+ <p>
+ Own Id: OTP-16260 Aux Id: OTP-15403 </p>
+ </item>
+ <item>
+ <p>
+ The new experimental <c>socket</c> module has been moved
+ to the Kernel application.</p>
+ <p>
+ Own Id: OTP-16312</p>
+ </item>
+ <item>
+ <p>
+ Replace usage of deprecated function in the <c>group</c>
+ module.</p>
+ <p>
+ Own Id: OTP-16345</p>
+ </item>
+ <item>
+ <p>
+ Minor updates due to the new spawn improvements made.</p>
+ <p>
+ Own Id: OTP-16368 Aux Id: OTP-15251 </p>
+ </item>
+ <item>
+ <p>
+ Update of <seeerl
+ marker="kernel:seq_trace#whatis">sequential
+ tracing</seeerl> to also support other information
+ transfers than message passing.</p>
+ <p>
+ Own Id: OTP-16370 Aux Id: OTP-15251, OTP-15232 </p>
+ </item>
+ <item>
+ <p><c>code:module_status/1</c> now accepts a list of
+ modules. <c>code:module_status/0</c>, which returns the
+ statuses for all loaded modules, has been added.</p>
+ <p>
+ Own Id: OTP-16402</p>
+ </item>
+ <item>
+ <p><c>filelib:wildcard/1,2</c> is now twice as fast when
+ a double star (<c>**</c>) is part of the pattern.</p>
+ <p>
+ Own Id: OTP-16419</p>
+ </item>
+ <item>
+ <p> A new implementation of distributed named process
+ groups has been introduced. It is available in the
+ <seeerl marker="kernel:pg"><c>pg</c></seeerl> module.
+ </p><p> Note that this <c>pg</c> module only has the name
+ in common with the experimental <c>pg</c> module that was
+ present in <c>stdlib</c> up until OTP 17. </p><p> Thanks
+ to Maxim Fedorov for the implementation. </p>
+ <p>
+ Own Id: OTP-16453 Aux Id: PR-2524 </p>
+ </item>
+ <item>
+ <p> The <seeerl marker="kernel:pg2"><c>pg2</c></seeerl>
+ module has been deprecated. It has also been scheduled
+ for removal in OTP 24. </p><p> You are advised to replace
+ the usage of <c>pg2</c> with the newly introduced <seeerl
+ marker="kernel:pg"><c>pg</c></seeerl> module. <c>pg</c>
+ has a similar API, but with a more scalable
+ implementation. </p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16455</p>
+ </item>
+ <item>
+ <p>Refactored the internal handling of deprecated and
+ removed functions.</p>
+ <p>
+ Own Id: OTP-16469</p>
+ </item>
+ <item>
+ <p>
+ The internal hosts file resolver cache <c>inet_hosts</c>
+ has been rewritten to behave better when the hosts file
+ changes. For example the cache is updated per entry
+ instead of cleared and reloaded so lookups do not
+ temporarily fail during reloading, and; when multiple
+ processes simultaneously request reload these are now
+ folded into one instead of all done in sequence. Reported
+ and first solution suggestion by Maxim Fedorov.</p>
+ <p>
+ Own Id: OTP-16487 Aux Id: PR-2516 </p>
+ </item>
+ <item>
+ <p>
+ Add <c>code:all_available/0</c> that can be used to get
+ all available modules.</p>
+ <p>
+ Own Id: OTP-16494</p>
+ </item>
+ <item>
+ <p>
+ As of OTP 23, the distributed <seeerl
+ marker="kernel:disk_log"><c>disk_log</c></seeerl> feature
+ has been deprecated. It has also been scheduled for
+ removal in OTP 24.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16495</p>
+ </item>
+ <item>
+ <p>
+ Add the function <c>code:fetch_docs/1</c> for fetching
+ embedded documentation for aa Erlang module.</p>
+ <p>
+ Own Id: OTP-16499</p>
+ </item>
+ <item>
+ <p>
+ Improve configure for the net nif, which should increase
+ portability.</p>
+ <p>
+ Own Id: OTP-16530 Aux Id: OTP-16464 </p>
+ </item>
+ <item>
+ <p>
+ socket: Socket counters and socket global counters are
+ now represented as maps (instead of property lists).</p>
+ <p>
+ Own Id: OTP-16535</p>
+ </item>
+ <item>
+ <p>
+ The experimental socket module has gotten restrictions
+ removed so now the 'seqpacket' socket type should work
+ for any communication domain (protocol family) where the
+ OS supports it, typically the Unix Domain.</p>
+ <p>
+ Own Id: OTP-16550 Aux Id: ERIERL-476 </p>
+ </item>
+ <item>
+ <p>
+ Allow using custom IO devices in <c>logger_std_h</c>.</p>
+ <p>
+ Own Id: OTP-16563 Aux Id: PR-2523 </p>
+ </item>
+ <item>
+ <p>Added <c>file:del_dir_r/1</c> which deletes a
+ directory together with all of its contents, similar to
+ <c>rm -rf</c> on Unix systems.</p>
+ <p>
+ Own Id: OTP-16570 Aux Id: PR-2565 </p>
+ </item>
+ <item>
+ <p>
+ socket: By default the socket options rcvtimeo and
+ sndtimeo are now disabled. To enable these, OTP now has
+ to be built with the configure option
+ --enable-esock-rcvsndtimeo</p>
+ <p>
+ Own Id: OTP-16620</p>
+ </item>
+ <item>
+ <p>
+ The experimental gen_tcp compatibility code utilizing the
+ socket module could loose buffered data when receiving a
+ specified number of bytes. This bug has been fixed.
+ Reported by Maksim Lapshin on bugs.erlang.org ERL-1234</p>
+ <p>
+ Own Id: OTP-16632 Aux Id: ERL-1234 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Kernel 6.5.2.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix bug in <c>application:loaded_applications/0</c> that
+ could cause it to fail with <c>badarg</c> if for example
+ a concurrent upgrade/downgrade is running.</p>
+ <p>
+ Own Id: OTP-16627 Aux Id: PR-2601 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Kernel 6.5.2</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/kernel/doc/src/part.xml b/lib/kernel/doc/src/part.xml
index 7a7a92b067..dd5d955a78 100644
--- a/lib/kernel/doc/src/part.xml
+++ b/lib/kernel/doc/src/part.xml
@@ -32,6 +32,8 @@
<p></p>
</description>
<xi:include href="introduction_chapter.xml"/>
+ <xi:include href="socket_usage.xml"/>
<xi:include href="logger_chapter.xml"/>
<xi:include href="logger_cookbook.xml"/>
+ <xi:include href="eep48_chapter.xml"/>
</part>
diff --git a/lib/kernel/doc/src/ref_man.xml b/lib/kernel/doc/src/ref_man.xml
index 9127157eb5..333cb83bee 100644
--- a/lib/kernel/doc/src/ref_man.xml
+++ b/lib/kernel/doc/src/ref_man.xml
@@ -69,6 +69,7 @@
<xi:include href="pg2.xml"/>
<xi:include href="rpc.xml"/>
<xi:include href="seq_trace.xml"/>
+ <xi:include href="socket.xml"/>
<xi:include href="user.xml"/>
<xi:include href="wrap_log_reader.xml"/>
<xi:include href="zlib_stub.xml"/>
diff --git a/lib/kernel/doc/src/socket.xml b/lib/kernel/doc/src/socket.xml
new file mode 100644
index 0000000000..5a9fdf98f7
--- /dev/null
+++ b/lib/kernel/doc/src/socket.xml
@@ -0,0 +1,1204 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2018</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>socket</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ <file>socket.xml</file>
+ </header>
+ <module since="OTP 22.0">socket</module>
+ <modulesummary>Socket interface.</modulesummary>
+ <description>
+ <p>This module provides an API for network socket.
+ Functions are provided to create, delete and manupilate the sockets
+ aswell as sending and reciving data on them. </p>
+ <p>The intent is that it shall be as "close as possible" to the OS
+ level socket interface. The only significant addition is that some of
+ the functions,
+ e.g. <seemfa marker="#recv/3"><c>recv/3</c></seemfa>,
+ has a timeout argument. </p>
+ <note>
+ <p>Some functions allow for an <i>asynchronous</i> call.
+ This is achieved by setting the <c>Timeout</c> argument to
+ <c>nowait</c>. For instance, if calling the
+ <seeerl marker="#recv_async"><c>recv/3</c></seeerl>
+ function with Timeout set to <c>nowait</c> (<c>recv(Sock, 0, nowait)</c>)
+ when there is actually nothing to read, it will return with
+ <c>{select, </c>
+ <seetype marker="#select_info"><c>SelectInfo</c></seetype><c>}</c>
+ (<c>SelectInfo</c> contains the
+ <seetype marker="socket#select_ref">SelectRef</seetype>).
+ When data eventually arrives a 'select' message
+ will be sent to the caller: </p>
+ <taglist>
+ <!-- NOTE THAT THE EMPTY TAG IS INTENTIONAL -->
+ <tag></tag>
+ <item><c>{'$socket', socket(), select, SelectRef}</c></item>
+ </taglist>
+ <p>The caller can now make another
+ call to the recv function and now expect data.</p>
+ <p>Note that all other users are <em>locked out</em> until the
+ 'current user' has called the function (recv in this case).</p>
+ <p>Another message the user must be prepared for (when making asynchronous
+ calls) is the <c>abort</c> message:</p>
+ <taglist>
+ <!-- NOTE THAT THE EMPTY TAG IS INTENTIONAL -->
+ <tag></tag>
+ <item><c>{'$socket', socket(), abort, Info}</c></item>
+ </taglist>
+ <p>This message indicates
+ that the (asynchronous) operation has been aborted.
+ If, for instance, the socket has been closed (by another process),
+ <c>Info</c> will be <c>{SelectRef, closed}</c>. </p>
+ </note>
+ <note>
+ <p>There is currently <em>no</em> support for Windows. </p>
+ <p>Support for IPv6 has been implemented but <em>not</em> tested. </p>
+ <p>SCTP has only been partly implemented (and not tested). </p>
+ </note>
+ </description>
+
+ <datatypes>
+ <datatype>
+ <name name="domain"/>
+ </datatype>
+ <datatype>
+ <name name="type"/>
+ </datatype>
+ <datatype>
+ <name name="protocol"/>
+ </datatype>
+ <datatype>
+ <name>socket()</name>
+ <desc><p>As returned by
+ <seemfa marker="#open/1"><c>open/1,2,3,4</c></seemfa> and
+ <seemfa marker="#accept/1"><c>accept/1,2</c></seemfa>.</p>
+ </desc>
+ </datatype>
+ <datatype>
+ <name name="select_tag"/>
+ <desc>
+ <p>A tag that describes the (select) operation.</p>
+ </desc>
+ </datatype>
+ <datatype>
+ <name name="select_ref"/>
+ <desc>
+ <p>A reference that uniquely identifies the (select) operation.</p>
+ </desc>
+ </datatype>
+ <datatype>
+ <name name="select_info"/>
+ </datatype>
+ <datatype>
+ <name name="socket_counters"/>
+ </datatype>
+ <datatype>
+ <name name="socket_info"/>
+ </datatype>
+ <datatype>
+ <name name="ip4_address"/>
+ </datatype>
+ <datatype>
+ <name name="ip6_address"/>
+ </datatype>
+ <datatype>
+ <name name="sockaddr"/>
+ </datatype>
+ <datatype>
+ <name name="sockaddr_in4"/>
+ </datatype>
+ <datatype>
+ <name name="sockaddr_in6"/>
+ </datatype>
+ <datatype>
+ <name name="sockaddr_un"/>
+ </datatype>
+ <datatype>
+ <name name="sockaddr_ll"/>
+ </datatype>
+ <datatype>
+ <name name="packet_type"/>
+ </datatype>
+ <datatype>
+ <name name="port_number"/>
+ </datatype>
+ <datatype>
+ <name name="in6_flow_info"/>
+ </datatype>
+ <datatype>
+ <name name="in6_scope_id"/>
+ </datatype>
+ <datatype>
+ <name name="send_flags"/>
+ </datatype>
+ <datatype>
+ <name name="send_flag"/>
+ </datatype>
+ <datatype>
+ <name name="recv_flags"/>
+ </datatype>
+ <datatype>
+ <name name="recv_flag"/>
+ </datatype>
+ <datatype>
+ <name name="shutdown_how"/>
+ </datatype>
+ <datatype>
+ <name name="sockopt_level"/>
+ </datatype>
+ <datatype>
+ <name name="otp_socket_option"/>
+ </datatype>
+ <datatype>
+ <name name="socket_option"/>
+ </datatype>
+ <datatype>
+ <name name="ip_socket_option"/>
+ </datatype>
+ <datatype>
+ <name name="ipv6_socket_option"/>
+ </datatype>
+ <datatype>
+ <name name="tcp_socket_option"/>
+ </datatype>
+ <datatype>
+ <name name="udp_socket_option"/>
+ </datatype>
+ <datatype>
+ <name name="sctp_socket_option"/>
+ </datatype>
+ <datatype>
+ <name name="timeval"/>
+ </datatype>
+ <datatype>
+ <name name="ip_tos"/>
+ </datatype>
+ <datatype>
+ <name name="ip_mreq"/>
+ </datatype>
+ <datatype>
+ <name name="ip_mreq_source"/>
+ </datatype>
+ <datatype>
+ <name name="ip_pmtudisc"/>
+ </datatype>
+ <datatype>
+ <name name="ip_msfilter_mode"/>
+ </datatype>
+ <datatype>
+ <name name="ip_msfilter"/>
+ </datatype>
+ <datatype>
+ <name name="ip_pktinfo"/>
+ </datatype>
+ <datatype>
+ <name name="ipv6_mreq"/>
+ </datatype>
+ <datatype>
+ <name name="ipv6_pmtudisc"/>
+ </datatype>
+ <datatype>
+ <name name="ipv6_pktinfo"/>
+ </datatype>
+ <datatype>
+ <name name="sctp_assoc_id"/>
+ </datatype>
+ <datatype>
+ <name name="sctp_sndrcvinfo"/>
+ </datatype>
+ <datatype>
+ <name name="sctp_event_subscribe"/>
+ </datatype>
+ <datatype>
+ <name name="sctp_assocparams"/>
+ </datatype>
+ <datatype>
+ <name name="sctp_initmsg"/>
+ </datatype>
+ <datatype>
+ <name name="sctp_rtoinfo"/>
+ </datatype>
+ <datatype>
+ <name name="msghdr_flag"/>
+ </datatype>
+ <datatype>
+ <name name="msghdr_flags"/>
+ </datatype>
+ <datatype>
+ <name name="msghdr"/>
+ </datatype>
+ <datatype>
+ <name name="cmsghdr_level"/>
+ </datatype>
+ <datatype>
+ <name name="cmsghdr_type"/>
+ </datatype>
+ <!--
+ <datatype>
+ <name name="cmsghdr_data"/>
+ </datatype>
+ -->
+ <datatype>
+ <name name="cmsghdr_recv"/>
+ </datatype>
+ <datatype>
+ <name name="cmsghdr_send"/>
+ </datatype>
+ <datatype>
+ <name name="icmp_dest_unreach"/>
+ </datatype>
+ <datatype>
+ <name name="icmpv6_dest_unreach"/>
+ </datatype>
+ <datatype>
+ <name name="ee_origin"/>
+ </datatype>
+ <datatype>
+ <name name="extended_err"/>
+ </datatype>
+ <datatype>
+ <name name="uint8"/>
+ </datatype>
+ <datatype>
+ <name name="uint16"/>
+ </datatype>
+ <datatype>
+ <name name="uint20"/>
+ </datatype>
+ <datatype>
+ <name name="uint32"/>
+ </datatype>
+ <datatype>
+ <name name="int32"/>
+ </datatype>
+ <datatype>
+ <name name="errcode"/>
+ <desc>
+ <p>
+ The POSIX error codes are mostly come from the
+ OS level socket interface,
+ but this module may generate some appropriate
+ POSIX codes.
+ </p>
+ <p>
+ The other values come from this module's lower levels
+ and are all fairly fatal internal errors:
+ </p>
+ <taglist>
+ <tag><c>exalloc</c></tag>
+ <item>Memory allocation failed</item>
+ <tag><c>exmonitor</c></tag>
+ <item>Failed to set a monitor on a process</item>
+ <tag><c>exselect</c></tag>
+ <item>Select operation failed</item>
+ <tag><c>exself</c></tag>
+ <item>Failed to get current process</item>
+ </taglist>
+ </desc>
+ </datatype>
+ </datatypes>
+
+ <funcs>
+ <func>
+ <name name="accept" arity="1" since="OTP 22.0"/>
+ <name name="accept" arity="2" clause_i="2" since="OTP 22.0"/>
+ <fsummary>Accept a connection on a socket.</fsummary>
+ <desc>
+ <p>Accept a connection on a socket.</p>
+ <p>This call is used with connection-based socket types
+ (<c>stream</c> or <c>seqpacket</c>). It extracs the first pending
+ connection request for the listen socket and returns the (newly)
+ connected socket.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="accept" arity="2" clause_i="1" anchor="accept_async" since="OTP 22.1"/>
+ <fsummary>Accept a connection on a socket.</fsummary>
+ <desc>
+ <p>Accept a connection on a socket.</p>
+
+ <p>This call is used with connection-based socket types
+ (<c>stream</c> or <c>seqpacket</c>). It extracs the first pending
+ connection request for the listen socket and returns the (newly)
+ connected socket.</p>
+
+ <p>In the case when there is no connections waiting, the function
+ will return with the <c>SelectInfo</c>. The caller can then await a
+ select message, <c>{'$socket', Socket, select, Info}</c> (where
+ <c>Info</c> is the
+ <seetype marker="socket#select_ref"><c>ref</c></seetype>
+ field from the <c>SelectInfo</c>),
+ when a client connects (a subsequent call to accept will then return
+ the socket). </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="bind" arity="2" since="OTP 22.0"/>
+ <fsummary>Bind a name to a socket.</fsummary>
+ <desc>
+ <p>Bind a name to a socket.</p>
+ <p>When a socket is created
+ (with <seemfa marker="#open/2"><c>open</c></seemfa>),
+ it has no address assigned to it. <c>bind</c> assigns the
+ address specified by the <c>Addr</c> argument.</p>
+ <p>The rules used for name binding vary between domains.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="cancel" arity="2" since="OTP 22.1"/>
+ <fsummary>Cancel an asynchronous request.</fsummary>
+ <desc>
+ <p>Cancel an asynchronous request.</p>
+
+ <p>Call this function in order to cancel a previous
+ asynchronous call to, e.g.
+ <seemfa marker="#recv/3"><c>recv/3</c></seemfa>. </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="close" arity="1" since="OTP 22.0"/>
+ <fsummary>Close a socket.</fsummary>
+ <desc>
+ <p>Closes the socket.</p>
+
+ <note>
+ <p>Note that for e.g. <c>protocol</c> = <c>tcp</c>, most implementations
+ doing a close does not guarantee that any data sent is delivered to
+ the recipient before the close is detected at the remote side. </p>
+ <p>One way to handle this is to use the
+ <seemfa marker="#shutdown/2"><c>shutdown</c></seemfa>
+ function
+ (<c>socket:shutdown(Socket, write)</c>) to signal that no more data is
+ to be sent and then wait for the read side of the socket to be closed.</p>
+ </note>
+ </desc>
+ </func>
+
+ <func>
+ <name name="connect" arity="2" since="OTP 22.0"/>
+ <name name="connect" arity="3" clause_i="2" since="OTP 22.0"/>
+ <fsummary>Initiate a connection on a socket.</fsummary>
+ <desc>
+ <p>This function connects the socket to the address
+ specied by the <c>SockAddr</c> argument.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="connect" arity="3" clause_i="1" anchor="connect_async" since="OTP 22.1"/>
+ <fsummary>Initiate a connection on a socket.</fsummary>
+ <desc>
+ <p>This function connects the socket to the address
+ specied by the <c>SockAddr</c> argument.</p>
+
+ <p>In the case when its not possible to immediately establish a
+ connection, the function will return with the
+ <seetype marker="#select_info"><c>SelectInfo</c></seetype>.
+ The caller can then await a
+ select message, <c>{'$socket', Socket, select, Info}</c> (where
+ <c>Info</c> is the
+ <seetype marker="socket#select_ref"><c>ref</c></seetype>
+ field from the <c>SelectInfo</c>,
+ a subsequent call to connect will then
+ establish the connection). </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="getopt" arity="3" clause_i="1" since="OTP 22.0"/>
+ <name name="getopt" arity="3" clause_i="2" since="OTP 22.0"/>
+ <name name="getopt" arity="3" clause_i="3" since="OTP 22.0"/>
+ <name name="getopt" arity="3" clause_i="4" since="OTP 22.0"/>
+ <name name="getopt" arity="3" clause_i="5" since="OTP 22.0"/>
+ <name name="getopt" arity="3" clause_i="6" since="OTP 22.0"/>
+ <name name="getopt" arity="3" clause_i="7" since="OTP 22.0"/>
+ <fsummary>Get an option on a socket.</fsummary>
+ <desc>
+ <p>Get an option on a socket.</p>
+ <p>What properties are valid depend both on <c>Level</c> and
+ on what kind of socket it is (<c>domain</c>, <c>type</c> and
+ <c>protocol</c>).</p>
+
+ <p>See the
+ <seeguide marker="socket_usage#socket_options">socket options</seeguide>
+ chapter of the users guide for more info. </p>
+
+ <note><p>Not all options are valid on all platforms. That is,
+ even if "we" support an option, that does not mean that the
+ underlying OS does.</p></note>
+
+ </desc>
+ </func>
+
+ <func>
+ <name name="getopt" arity="3" clause_i="8" since="OTP 22.0"/>
+ <fsummary>Get an option on a socket.</fsummary>
+ <desc>
+ <p>Get an option on a socket.</p>
+
+ <p>When specifying <c>Level</c> as an integer, and therefor
+ using "native mode", it is *currently* up to the caller to
+ know how to interpret the result.</p>
+
+ <p>For more info, see
+ <seemfa marker="#getopt/3">getopt</seemfa> above. </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="info" arity="1" since="OTP 22.1"/>
+ <fsummary>Get miscellaneous socket info.</fsummary>
+ <desc>
+ <p>Get miscellaneous info about the socket.</p>
+ <p>The function returns a map with each info item as a key-value
+ binding. It reflects the "current" state of the socket. </p>
+ <note>
+ <p>In order to ensure data integrity, mutex'es are taken when
+ needed. So, do not call this function often. </p>
+ </note>
+ </desc>
+ </func>
+
+ <func>
+ <name name="listen" arity="1" since="OTP 22.0"/>
+ <name name="listen" arity="2" since="OTP 22.0"/>
+ <fsummary>Listen for connections on a socket.</fsummary>
+ <desc>
+ <p>Listen for connections on a socket.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="number_of" arity="0" since="OTP 22.3"/>
+ <fsummary>Get the number of active sockets.</fsummary>
+ <desc>
+ <p>Returns the number of active sockets.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="open" arity="1" since="OTP 23.0"/>
+ <name name="open" arity="2" clause_i="1" since="OTP 23.0"/>
+ <fsummary>Create an endpoint for communication.</fsummary>
+ <desc>
+ <p>Create an endpoint (socket) for communication based on an
+ already existing file descriptor.
+ The function attempts to retrieve domain, type and protocol from
+ the system. This is however not possible on all platforms, and
+ in those cases it expects it in <c>Opts</c>. </p>
+
+ <p>The <c>Opts</c> argument is intended for providing extra
+ information for the open call:</p>
+ <taglist>
+ <tag><c><![CDATA[dup: boolean()]]></c></tag>
+ <item>
+ <p>Shall the provided descriptor be duplicated (dup) or not.
+ <br/>Defaults to <c>true</c>. </p>
+ </item>
+
+ <tag><c><![CDATA[debug: boolean()]]></c></tag>
+ <item>
+ <p>Enable or disable debug during the open call.
+ <br/>Defaults to <c>false</c>. </p>
+ </item>
+
+ <tag><c><![CDATA[domain: socket:domain()]]></c></tag>
+ <item>
+ <p>Which domain is the descriptor of. </p>
+ </item>
+
+ <tag><c><![CDATA[type: socket:type()]]></c></tag>
+ <item>
+ <p>Which type is the descriptor of. </p>
+ </item>
+
+ <tag><c><![CDATA[protocol: socket:protocol()]]></c></tag>
+ <item>
+ <p>Which protocol is the descriptor of. </p>
+ </item>
+
+ </taglist>
+
+ <note>
+ <p>This function should be used with care! </p>
+ <p>On some platforms its <em>necessary</em> to provide the
+ <c>protocol</c> as its impossible to retrieve it. </p>
+ </note>
+ </desc>
+ </func>
+
+ <func>
+ <name name="open" arity="2" clause_i="2" since="OTP 22.0"/>
+ <name name="open" arity="3" since="OTP 22.0"/>
+ <name name="open" arity="4" since="OTP 22.0"/>
+ <fsummary>Create an endpoint for communication.</fsummary>
+ <desc>
+ <p>Creates an endpoint (socket) for communication.</p>
+
+ <p>For some <c>types</c> there is a default protocol,
+ indicated by <c>default</c>, which it <em>may</em> be
+ possible to specify.
+ And for <c>Domain = local</c>, if a protocol <em>is</em> pecified,
+ it <em>must</em> be <c>default</c>. </p>
+
+ <p>The <c>Opts</c> argument is intended for "other" options.
+ Currently the only supported option(s) are <c>netns</c>, which
+ is only supported on the linux platform and <c>debug</c> (controls debug
+ printouts during the open call).</p>
+
+ <note>
+ <p>It may not be possible to specify the default protocol (except
+ when <c>Domain = local</c>). We need to be able to retreive
+ the resulting protocol, which is <em>not</em> possble on all
+ platforms. </p>
+ </note>
+ </desc>
+ </func>
+
+ <func>
+ <name name="peername" arity="1" since="OTP 22.0"/>
+ <fsummary>Get name of connected socket peer.</fsummary>
+ <desc>
+ <p>Returns the address of the peer connected to the socket.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="recv" arity="1" since="OTP 22.0"/>
+ <name name="recv" arity="2" since="OTP 22.0"/>
+ <name name="recv" arity="3" clause_i="1" since="OTP 22.0"/>
+ <name name="recv" arity="3" clause_i="3" since="OTP 22.0"/>
+ <name name="recv" arity="4" clause_i="2" since="OTP 22.0"/>
+ <fsummary>Receive a message from a socket.</fsummary>
+ <desc>
+ <p>Receive a message from a socket.</p>
+ <p>There is a special case for the argument <c>Length</c>.
+ If it is set to zero (0), it means "give me everything you
+ currently have".</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="recv" arity="3" clause_i="2" anchor="recv_async" since="OTP 22.1"/>
+ <name name="recv" arity="4" clause_i="1" since="OTP 22.1"/>
+ <fsummary>Receive a message from a socket.</fsummary>
+ <desc>
+ <p>Receive a message from a socket.</p>
+
+ <p>There is a special case for the argument <c>Length</c>.
+ If it is set to zero (0), it means "give me everything you
+ currently have".</p>
+
+ <p>In the case when there is no data waiting, the function
+ will return with the <c>SelectInfo</c>. The caller can then await a
+ select message, <c>{'$socket', Socket, select, Info}</c> (where
+ <c>Info</c> is the
+ <seetype marker="socket#select_ref"><c>ref</c></seetype>
+ field from the <c>SelectInfo</c>),
+ when data has arrived (a subsequent call to recv will then return
+ the data). </p>
+ <p>Note that if a length (<c>> 0</c>) is specified, and only part
+ of that amount of data is available, the function will return with
+ that data <em>and</em> the <c>SelectInfo</c> (if the caller don't
+ want to wait for the remaining data, it must immediately call
+ the <seemfa marker="#cancel/2"><c>cancel/2</c></seemfa> function.)</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="recvfrom" arity="1" since="OTP 22.0"/>
+ <name name="recvfrom" arity="2" since="OTP 22.0"/>
+ <name name="recvfrom" arity="3" clause_i="2" since="OTP 22.0"/>
+ <name name="recvfrom" arity="3" clause_i="3" since="OTP 22.0"/>
+ <name name="recvfrom" arity="3" clause_i="5" since="OTP 22.0"/>
+ <name name="recvfrom" arity="4" clause_i="2" since="OTP 22.0"/>
+ <fsummary>Receive a message from a socket.</fsummary>
+ <desc>
+ <p>Receive a message from a socket.</p>
+ <p>This function reads "messages", which means that regardless of
+ how much we want to read, it returns when we get a message
+ (if the buffer size is too small, the message will be truncated).</p>
+ <p>The <c>BufSz</c> argument basically defines the size of the
+ receive buffer. By setting the value to zero (0), the configured
+ size (setopt with <c>Level</c> = <c>otp</c> and
+ <c>Key</c> = <c>rcvbuf</c>) is used.</p>
+ <p>It may be impossible to know what (buffer) size is appropriate
+ "in advance", and in those cases it may be convenient to use the
+ (recv) 'peek' flag. When this flag is provided, the message is *not*
+ "consumed" from the underlying buffers, so another recvfrom call
+ is needed, possibly with a then adjusted buffer size.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="recvfrom" arity="3" clause_i="1" anchor="recvfrom_async" since="OTP 22.1"/>
+ <name name="recvfrom" arity="3" clause_i="4" since="OTP 22.1"/>
+ <name name="recvfrom" arity="4" clause_i="1" since="OTP 22.1"/>
+ <fsummary>Receive a message from a socket.</fsummary>
+ <desc>
+ <p>Receive a message from a socket.</p>
+ <p>This function reads "messages", which means that regardless of
+ how much we want to read, it returns when we get a message
+ (if the buffer size is too small, the message will be truncated).</p>
+ <p>The <c>BufSz</c> argument basically defines the size of the
+ receive buffer. By setting the value to zero (0), the configured
+ size (setopt with <c>Level</c> = <c>otp</c> and
+ <c>Key</c> = <c>rcvbuf</c>) is used.</p>
+ <p>It may be impossible to know what (buffer) size is appropriate
+ "in advance", and in those cases it may be convenient to use the
+ (recv) 'peek' flag. When this flag is provided, the message is *not*
+ "consumed" from the underlying buffers, so another recvfrom call
+ is needed, possibly with a then adjusted buffer size.</p>
+
+ <p>In the case when there is no data waiting, the function
+ will return with the <c>SelectInfo</c>. The caller can then await a
+ select message, <c>{'$socket', Socket, select, Info}</c> (where
+ <c>Info</c> is the
+ <seetype marker="socket#select_ref"><c>ref</c></seetype>
+ field from the <c>SelectInfo</c>),
+ when data has arrived (a subsequent call to recvfrom will then return
+ the data). </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="recvmsg" arity="1" since="OTP 22.0"/>
+ <name name="recvmsg" arity="2" clause_i="1" since="OTP 22.0"/>
+ <name name="recvmsg" arity="2" clause_i="3" since="OTP 22.0"/>
+ <name name="recvmsg" arity="3" clause_i="2" since="OTP 22.0"/>
+ <name name="recvmsg" arity="3" clause_i="3" since="OTP 22.0"/>
+ <name name="recvmsg" arity="5" clause_i="2" since="OTP 22.0"/>
+ <fsummary>Receive a message from a socket.</fsummary>
+ <desc>
+ <p>Receive a message from a socket.</p>
+ <p>This function reads "messages", which means that regardless of
+ how much we want to read, it returns when we get a message.</p>
+ <p>The message will be delivered in the form of a <c>msghdr()</c>,
+ which may contain the source address (if socket not connected),
+ a list of <c>cmsghdr_recv()</c> (depends on what socket options have
+ been set and what the protocol and platform supports) and
+ also a set of flags, providing further info about the read. </p>
+
+ <p>The <c>BufSz</c> argument basically defines the size of the
+ receive buffer. By setting the value to zero (0), the configured
+ size (setopt with <c>Level</c> = <c>otp</c> and
+ <c>Key</c> = <c>rcvbuf</c>) is used.</p>
+
+ <p>The <c>CtrlSz</c> argument basically defines the size of the
+ receive buffer for the control messages.
+ By setting the value to zero (0), the configured size (setopt
+ with <c>Level</c> = <c>otp</c>) is used.</p>
+
+ <p>It may be impossible to know what (buffer) size is appropriate
+ "in advance", and in those cases it may be convenient to use the
+ (recv) 'peek' flag. When this flag is provided, the message is *not*
+ "consumed" from the underlying buffers, so another recvmsg call
+ is needed, possibly with a then adjusted buffer size.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="recvmsg" arity="2" clause_i="2" anchor="recvmsg_async" since="OTP 22.1"/>
+ <name name="recvmsg" arity="3" clause_i="1" since="OTP 22.1"/>
+ <name name="recvmsg" arity="5" clause_i="1" since="OTP 22.1"/>
+ <fsummary>Receive a message from a socket.</fsummary>
+ <desc>
+ <p>Receive a message from a socket.</p>
+ <p>This function reads "messages", which means that regardless of
+ how much we want to read, it returns when we get a message.</p>
+ <p>The message will be delivered in the form of a <c>msghdr()</c>,
+ which may contain the source address (if socket not connected),
+ a list of <c>cmsghdr_recv()</c> (depends on what socket options have
+ been set and what the protocol and platform supports) and
+ also a set of flags, providing further info about the read. </p>
+
+ <p>The <c>BufSz</c> argument basically defines the size of the
+ receive buffer. By setting the value to zero (0), the configured
+ size (setopt with <c>Level</c> = <c>otp</c> and
+ <c>Key</c> = <c>rcvbuf</c>) is used.</p>
+
+ <p>The <c>CtrlSz</c> argument basically defines the size of the
+ receive buffer for the control messages.
+ By setting the value to zero (0), the configured size (setopt
+ with <c>Level</c> = <c>otp</c>) is used.</p>
+
+ <p>It may be impossible to know what (buffer) size is appropriate
+ "in advance", and in those cases it may be convenient to use the
+ (recv) 'peek' flag. When this flag is provided, the message is *not*
+ "consumed" from the underlying buffers, so another recvmsg call
+ is needed, possibly with a then adjusted buffer size.</p>
+
+ <p>In the case when there is no data waiting, the function
+ will return with the <c>SelectInfo</c>. The caller can then await a
+ select message, <c>{'$socket', Socket, select, Info}</c> (where
+ <c>Info</c> is the
+ <seetype marker="socket#select_ref"><c>ref</c></seetype>
+ field from the <c>SelectInfo</c>),
+ when data has arrived (a subsequent call to recvmsg will then return
+ the data). </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="send" arity="2" since="OTP 22.0"/>
+ <name name="send" arity="3" clause_i="1" since="OTP 22.0"/>
+ <name name="send" arity="3" clause_i="3" since="OTP 22.0"/>
+ <name name="send" arity="4" clause_i="2" since="OTP 22.0"/>
+ <fsummary>Send a message on a socket.</fsummary>
+ <desc>
+ <p>Send a message on a connected socket.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="send" arity="3" clause_i="2" anchor="send_async" since="OTP 22.1"/>
+ <name name="send" arity="4" clause_i="1" since="OTP 22.1"/>
+ <fsummary>Send a message on a socket.</fsummary>
+ <desc>
+ <p>Send a message on a connected socket.</p>
+
+ <p>In the case when there is no room in the (system-) buffers,
+ the function will return with the <c>SelectInfo</c>. The caller
+ can then await a select message,
+ <c>{'$socket', Socket, select, Info}</c>
+ (where <c>Info</c> is the
+ <seetype marker="socket#select_ref"><c>ref</c></seetype>
+ field from the
+ <c>SelectInfo</c>), when there is room for more data (a subsequent
+ call to send will then send the data). </p>
+ <p>Note that if not all the data was sent, the function will return
+ with the remaining data <em>and</em> the <c>SelectInfo</c>
+ (if the caller don't
+ want to wait to be able to send the rest, it should immediately call
+ the <seemfa marker="#cancel/2"><c>cancel/2</c></seemfa> function.)</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="sendmsg" arity="2" since="OTP 22.0"/>
+ <name name="sendmsg" arity="3" clause_i="1" since="OTP 22.0"/>
+ <name name="sendmsg" arity="3" clause_i="3" since="OTP 22.0"/>
+ <name name="sendmsg" arity="4" clause_i="2" since="OTP 22.0"/>
+ <fsummary>Send a message on a socket.</fsummary>
+ <desc>
+ <p>Send a message on a socket. The destination, if needed
+ (socket <em>not</em> connected) is provided in the <c>MsgHdr</c>,
+ which also contains the message to send,
+ The <c>MsgHdr</c> may also contain an list of optional <c>cmsghdr_send()</c>
+ (depends on what the protocol and platform supports).</p>
+
+ <p>Unlike the <seemfa marker="#send/2"><c>send</c></seemfa> function,
+ this one sends <em>one message</em>.
+ This means that if, for whatever reason, its not possible to send the
+ message in one go, the function will instead return with the
+ <em>remaining</em> data (<c>{ok, Remaining}</c>). Thereby leaving it
+ up to the caller to decide what to do (retry with the remaining data
+ of give up). </p>
+
+ </desc>
+ </func>
+
+ <func>
+ <name name="sendmsg" arity="3" clause_i="2" anchor="sendmsg_async" since="OTP 22.1"/>
+ <name name="sendmsg" arity="4" clause_i="1" since="OTP 22.1"/>
+ <fsummary>Send a message on a socket.</fsummary>
+ <desc>
+ <p>Send a message on a socket. The destination, if needed
+ (socket <em>not</em> connected) is provided in the <c>MsgHdr</c>,
+ which also contains the message to send,
+ The <c>MsgHdr</c> may also contain an list of optional <c>cmsghdr_send()</c>
+ (depends on what the protocol and platform supports).</p>
+
+ <p>Unlike the <seemfa marker="#send/2"><c>send</c></seemfa> function,
+ this one sends <em>one message</em>.
+ This means that if, for whatever reason, its not possible to send the
+ message in one go, the function will instead return with the
+ <em>remaining</em> data (<c>{ok, Remaining}</c>). Thereby leaving it
+ up to the caller to decide what to do (retry with the remaining data
+ of give up). </p>
+
+ <p>In the case when there is no room in the (system-) buffers,
+ the function will return with the <c>SelectInfo</c>. The caller
+ can then await a select message,
+ <c>{'$socket', Socket, select, Info}</c>
+ (where <c>Info</c> is the
+ <seetype marker="socket#select_ref"><c>ref</c></seetype>
+ field from the
+ <c>SelectInfo</c>), when there is room for more data (a subsequent
+ call to sendmsg will then send the data). </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="sendto" arity="3" since="OTP 22.0"/>
+ <name name="sendto" arity="4" clause_i="1" since="OTP 22.0"/>
+ <name name="sendto" arity="4" clause_i="3" since="OTP 22.0"/>
+ <name name="sendto" arity="5" clause_i="2" since="OTP 22.0"/>
+ <fsummary>Send a message on a socket.</fsummary>
+ <desc>
+ <p>Send a message on a socket, to the specified destination.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="sendto" arity="4" clause_i="2" anchor="sendto_async" since="OTP 22.1"/>
+ <name name="sendto" arity="5" clause_i="1" since="OTP 22.1"/>
+ <fsummary>Send a message on a socket.</fsummary>
+ <desc>
+ <p>Send a message on a socket, to the specified destination.</p>
+
+ <p>In the case when there is no room in the (system-) buffers,
+ the function will return with the <c>SelectInfo</c>. The caller
+ can then await a select message,
+ <c>{'$socket', Socket, select, Info}</c>
+ (where <c>Info</c> is the
+ <seetype marker="socket#select_ref"><c>ref</c></seetype>
+ field from the
+ <c>SelectInfo</c>), when there is room for more data (a subsequent
+ call to sendto will then send the data). </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="setopt" arity="4" clause_i="1" since="OTP 22.0"/>
+ <name name="setopt" arity="4" clause_i="2" since="OTP 22.0"/>
+ <name name="setopt" arity="4" clause_i="3" since="OTP 22.0"/>
+ <name name="setopt" arity="4" clause_i="4" since="OTP 22.0"/>
+ <name name="setopt" arity="4" clause_i="5" since="OTP 22.0"/>
+ <name name="setopt" arity="4" clause_i="6" since="OTP 22.0"/>
+ <name name="setopt" arity="4" clause_i="7" since="OTP 22.0"/>
+ <name name="setopt" arity="4" clause_i="8" since="OTP 22.0"/>
+ <fsummary>Set an option on a socket.</fsummary>
+ <desc>
+ <p>Set an option on a socket.</p>
+ <p>What options are valid depend both on <c>Level</c> and on
+ what kind of socket it is (<c>domain</c>, <c>type</c> and
+ <c>protocol</c>).</p>
+
+ <p>See the
+ <seeguide marker="socket_usage#socket_options">socket options</seeguide>
+ chapter of the users guide for more info. </p>
+
+ <note><p>Not all options are valid on all platforms. That is,
+ even if "we" support an option, that does not mean that the
+ underlying OS does.</p></note>
+
+ <note><p>Sockets are set 'non-blocking' when created, so this option
+ is *not* available (as it would adversely effect the Erlang VM
+ to set a socket 'blocking').</p></note>
+ </desc>
+ </func>
+
+ <func>
+ <name name="setopt" arity="4" clause_i="8" since="OTP 22.0"/>
+ <fsummary>Set options on a socket.</fsummary>
+ <desc>
+ <p>Set options on a socket.</p>
+
+ <p>When specifying <c>Level</c> as an integer, and therefor
+ using "native mode", it is *currently* up to the caller to
+ know how to encode the <c>Value</c>.</p>
+
+ <p>For more info, see
+ <seemfa marker="#setopt/4">setopt</seemfa> above. </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="shutdown" arity="2" since="OTP 22.0"/>
+ <fsummary>Shut down part of a full-duplex connection.</fsummary>
+ <desc>
+ <p>Shut down all or part of a full-duplex connection.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="sockname" arity="1" since="OTP 22.0"/>
+ <fsummary>Get socket name.</fsummary>
+ <desc>
+ <p>Returns the current address to which the socket is bound.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="OTP 22.0">
+ supports() -> Supports
+ </name>
+ <name since="OTP 22.0">
+ supports(Key1 :: options) -> SupportsOptions
+ </name>
+ <name since="OTP 22.0">
+ supports(Key1 :: send_flags) -> SupportsSendFlags
+ </name>
+ <name since="OTP 22.0">
+ supports(Key1 :: recv_flags) -> SupportsRecvFlags
+ </name>
+ <name since="OTP 22.0">
+ supports(Key1 :: options, Key2 :: socket) -> SupportsOptionsSocket
+ </name>
+ <name since="OTP 22.0">
+ supports(Key1 :: options, Key2 :: ip) -> SupportsOptionsIP
+ </name>
+ <name since="OTP 22.0">
+ supports(Key1 :: options, Key2 :: ipv6) -> SupportsOptionsIPv6
+ </name>
+ <name since="OTP 22.0">
+ supports(Key1 :: options, Key2 :: tcp) -> SupportsOptionsTCP
+ </name>
+ <name since="OTP 22.0">
+ supports(Key1 :: options, Key2 :: udp) -> SupportsOptionsUDP
+ </name>
+ <name since="OTP 22.0">
+ supports(Key1 :: options, Key2 :: sctp) -> SupportsOptionsSCTP
+ </name>
+ <!--
+ <name name="supports" arity="0" since="OTP 22.0"/>
+ <name name="supports" arity="1" since="OTP 22.0"/>
+ <name name="supports" arity="2" since="OTP 22.0"/>
+ -->
+ <fsummary>Report info about what the platform supports.</fsummary>
+ <type>
+ <v>
+ Supports :: [{Feature,&nbsp;boolean()}
+ &nbsp;&nbsp;|&nbsp;{send_flags,&nbsp;SupportsSendFlags}
+ &nbsp;&nbsp;|&nbsp;{recv_flags,&nbsp;SupportsRecvFlags}
+ &nbsp;&nbsp;|&nbsp;{options,&nbsp;SupportsOptions}]
+ </v>
+ <v>Feature :: sctp | ipv6 | local | netns</v>
+ <v>
+ SupportsSendFlags ::
+ [{<seetype marker="#send_flag">send_flag()</seetype>,&nbsp;boolean()}]
+ </v>
+ <v>
+ SupportsRecvFlags ::
+ [{<seetype marker="#recv_flag">recv_flag()</seetype>,&nbsp;boolean()}]
+ </v>
+ <v>
+ SupportsOptions ::
+ [{socket,&nbsp;SupportsOptionsSocket}
+ &nbsp;&nbsp;|&nbsp;{ip,&nbsp;SupportsOptionsIP}
+ &nbsp;&nbsp;|&nbsp;{ipv6,&nbsp;SupportsOptionsIPv6}
+ &nbsp;&nbsp;|&nbsp;{tcp,&nbsp;SupportsOptionsTCP}
+ &nbsp;&nbsp;|&nbsp;{udp,&nbsp;SupportsOptionsUDP}
+ &nbsp;&nbsp;|&nbsp;{sctp,&nbsp;SupportsOptionsSCTP}]
+ </v>
+ <v>
+ SupportsOptionsSocket ::
+ [{<seetype marker="#socket_option">socket_option()</seetype>,&nbsp;boolean()}]
+ </v>
+ <v>
+ SupportsOptionsIP ::
+ [{<seetype marker="#ip_socket_option">ip_socket_option()</seetype>,&nbsp;boolean()}]
+ </v>
+ <v>
+ SupportsOptionsIPv6 ::
+ [{<seetype marker="#ipv6_socket_option">ipv6_socket_option()</seetype>,&nbsp;boolean()}]
+ </v>
+ <v>
+ SupportsOptionsTCP ::
+ [{<seetype marker="#tcp_socket_option">tcp_socket_option()</seetype>,&nbsp;boolean()}]
+ </v>
+ <v>
+ SupportsOptionsUDP ::
+ [{<seetype marker="#udp_socket_option">udp_socket_option()</seetype>,&nbsp;boolean()}]
+ </v>
+ <v>
+ SupportsOptionsSCTP ::
+ [{<seetype marker="#sctp_socket_option">sctp_socket_option()</seetype>,&nbsp;boolean()}]
+ </v>
+ </type>
+ <desc>
+ <p>
+ This function retreives information about what the
+ platform supports, such as if SCTP is supported,
+ or which socket options are supported.
+ </p>
+ <p>
+ For keys other than the known the empty list is returned,
+ Note that in a future version or on a different platform
+ there might be more supported items.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="OTP 23.0">
+ is_supported(Key1 :: sctp | ipv6 | local | netns) -> boolean()
+ </name>
+ <name since="OTP 23.0">
+ is_supported(Key1 :: send_flags, Key2 :: SendFlag) -> boolean()
+ </name>
+ <name since="OTP 23.0">
+ is_supported(Key1 :: recv_flags, Key2 :: RecvFlag) -> boolean()
+ </name>
+ <name since="OTP 23.0">
+ is_supported(Key1 :: options, Key2 :: socket, Key3 :: SocketOption) ->
+ boolean()
+ </name>
+ <name since="OTP 23.0">
+ is_supported(Key1 :: options, Key2 :: ip, Key3 :: IPSocketOption) ->
+ boolean()
+ </name>
+ <name since="OTP 23.0">
+ is_supported(Key1 :: options, Key2 :: ipv6, Key3 :: IPv6SocketOption) ->
+ boolean()
+ </name>
+ <name since="OTP 23.0">
+ is_supported(Key1 :: options, Key2 :: tcp, Key3 :: TCPSocketOption) ->
+ boolean()
+ </name>
+ <name since="OTP 23.0">
+ is_supported(Key1 :: options, Key2 :: udp, Key3 :: UDPSocketOption) ->
+ boolean()
+ </name>
+ <name since="OTP 23.0">
+ is_supported(Key1 :: options, Key2 :: sctp, Key3 :: SCTPSocketOption) ->
+ boolean()
+ </name>
+ <fsummary>Report info about what the platform supports.</fsummary>
+ <type>
+ <v>
+ SocketOption ::
+ <seetype marker="#socket_option">socket_option()</seetype>
+ </v>
+ <v>
+ IPSocketOption ::
+ <seetype marker="#ip_socket_option">ip_socket_option()</seetype>
+ </v>
+ <v>
+ IPv6SocketOption ::
+ <seetype marker="#ipv6_socket_option">ipv6_socket_option()</seetype>
+ </v>
+ <v>
+ TCPSocketOption ::
+ <seetype marker="#tcp_socket_option">tcp_socket_option()</seetype>
+ </v>
+ <v>
+ UDPSocketOption ::
+ <seetype marker="#udp_socket_option">udp_socket_option()</seetype>
+ </v>
+ <v>
+ SCTPSocketOption ::
+ <seetype marker="#sctp_socket_option">sctp_socket_option()</seetype>
+ </v>
+ </type>
+ <desc>
+ <p>
+ This function retreives information about what the
+ platform supports, such as if SCTP is supported,
+ or which socket options are supported.
+ </p>
+ <p>
+ For keys other than the known <c>false</c> is returned.
+ Note that in a future version or on a different platform
+ there might be more supported items.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="which_sockets" arity="0" since="OTP 22.3"/>
+ <name name="which_sockets" arity="1" since="OTP 22.3"/>
+ <fsummary>Get the current active sockets.</fsummary>
+ <desc>
+ <p>Returns a list of all sockets, according to the
+ filter rule.</p>
+ <p>There are several pre-made filter rule(s) and one general: </p>
+ <taglist>
+ <tag><c><![CDATA[inet | inet6]]></c></tag>
+ <item>
+ <p>Selection based on the domain of the socket.
+ <br/>Only a subset is valid. </p>
+ </item>
+
+ <tag><c><![CDATA[stream | dgram | seqpacket]]></c></tag>
+ <item>
+ <p>Selection based on the type of the socket.
+ <br/>Only a subset is valid. </p>
+ </item>
+
+ <tag><c><![CDATA[sctp | tcp | udp]]></c></tag>
+ <item>
+ <p>Selection based on the protocol of the socket.
+ <br/>Only a subset is valid. </p>
+ </item>
+
+ <tag><c><![CDATA[pid()]]></c></tag>
+ <item>
+ <p>Selection base on which sockets has this pid as
+ Controlling Process. </p>
+ </item>
+
+ <tag><c><![CDATA[fun((socket_info()) -> boolean())]]></c></tag>
+ <item>
+ <p>The general filter rule.
+ <br/>A fun that takes the socket info and returns a
+ <c><![CDATA[boolean()]]></c>
+ (<c><![CDATA[true]]></c> if the socket sould be included and
+ <c><![CDATA[false]]></c> if should not). </p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+
+ </funcs>
+ <section>
+ <title>Examples</title>
+ <marker id="examples"></marker>
+ <code type="none">
+client(Addr, SAddr, SPort) ->
+ {ok, Sock} = socket:open(inet, stream, tcp),
+ {ok, _} = socket:bind(Sock, #{family => inet,
+ addr => Addr}),
+ ok = socket:connect(Sock, #{family => inet,
+ addr => SAddr,
+ port => SPort}),
+ Msg = list_to_binary("hello"),
+ ok = socket:send(Sock, Msg),
+ ok = socket:shutdown(Sock, write),
+ {ok, Msg} = socket:recv(Sock),
+ ok = socket:close(Sock).
+
+server(Addr, Port) ->
+ {ok, LSock} = socket:open(inet, stream, tcp),
+ {ok, _} = socket:bind(LSock, #{family => inet,
+ port => Port,
+ addr => Addr}),
+ ok = socket:listen(LSock),
+ {ok, Sock} = socket:accept(LSock),
+ {ok, Msg} = socket:recv(Sock),
+ ok = socket:send(Sock, Msg),
+ ok = socket:shutdown(Sock, write),
+ ok = socket:close(Sock),
+ ok = socket:close(LSock).
+ </code>
+ </section>
+</erlref>
diff --git a/lib/kernel/doc/src/socket_usage.xml b/lib/kernel/doc/src/socket_usage.xml
new file mode 100644
index 0000000000..580ff4cbd0
--- /dev/null
+++ b/lib/kernel/doc/src/socket_usage.xml
@@ -0,0 +1,913 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>2018</year><year>2019</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>Socket Usage</title>
+ <prepared>Micael Karlberg</prepared>
+ <docno></docno>
+ <date>2018-07-17</date>
+ <rev>PA1</rev>
+ <file>socket_usage.xml</file>
+ </header>
+
+ <section>
+ <title>Introduction</title>
+ <p>The socket interface (module) is basically an "thin" layer on top of
+ the OS socket interface. It is assumed that, unless you have special needs,
+ gen_[tcp|udp|sctp] should be sufficent (when they become available). </p>
+ <p>Note that just because we have a documented and described option,
+ it does <em>not</em> mean that the OS supports it. So its recommended
+ that the user reads the platform specific documentation for the
+ option used. </p>
+ <section>
+ <title>Asynchronous calls</title>
+ <p>Some functions allow for an <i>asynchronous</i> call
+ (<seeerl marker="socket#accept_async"><c>accept/2</c></seeerl>,
+ <seeerl marker="socket#connect_async"><c>connect/3</c></seeerl>,
+ <seeerl marker="socket#recv_async"><c>recv/3,4</c></seeerl>,
+ <seeerl marker="socket#recvfrom_async"><c>recvfrom/3,4</c></seeerl>,
+ <seeerl marker="socket#recvmsg_async"><c>recvmsg/2,3,5</c></seeerl>,
+ <seeerl marker="socket#send_async"><c>send/3,4</c></seeerl>,
+ <seeerl marker="socket#sendmsg_async"><c>sendmsg/3,4</c></seeerl> and
+ <seeerl marker="socket#sendto_async"><c>sendto/4,5</c></seeerl>).
+ This is achieved by setting the <c>Timeout</c> argument to
+ <c>nowait</c>. For instance, if calling the
+ <seeerl marker="socket#recv_async"><c>recv/3</c></seeerl>
+ function with Timeout set to <c>nowait</c> (i.e.
+ <c>recv(Sock, 0, nowait)</c>)
+ when there is actually nothing to read, it will return with
+ <c>{select, </c>
+ <seetype marker="socket#select_info"><c>SelectInfo</c></seetype><c>}</c>
+ (<c>SelectInfo</c> contains the
+ <seetype marker="socket#select_ref">SelectRef</seetype>).
+ When data eventually arrives a 'select message'
+ will be sent to the caller:</p>
+ <taglist>
+ <!-- NOTE THAT THE EMPTY TAG IS INTENTIONAL -->
+ <tag></tag>
+ <item><c>{'$socket', socket(), select, SelectRef}</c></item>
+ </taglist>
+ <p>The caller can then make another
+ call to the recv function and now expect data.</p>
+ <p>Note that all other users are <em>locked out</em> until the
+ 'current user' has called the function (recv in this case). So either
+ immediately call the function or
+ <seemfa marker="socket#cancel/2"><c>cancel</c></seemfa>. </p>
+ <p>The user must also be prepared to receive an abort message: </p>
+ <taglist>
+ <!-- NOTE THAT THE EMPTY TAG IS INTENTIONAL -->
+ <tag></tag>
+ <item><c>{'$socket', socket(), abort, Info}</c></item>
+ </taglist>
+ <p>If the operation is aborted
+ for whatever reason (e.g. if the socket is closed "by someone else").
+ The <c>Info</c> part contains the abort reason (in this case that
+ the socket has been closed <c>Info = {SelectRef, closed}</c>). </p>
+
+ <p>The general form of the 'socket' message is: </p>
+ <taglist>
+ <!-- NOTE THAT THE EMPTY TAG IS INTENTIONAL -->
+ <tag></tag>
+ <item><c>{'$socket', Sock :: socket(), Tag :: atom(), Info :: term()}</c></item>
+ </taglist>
+ <p>Where the format of <c>Info</c> is a function of <c>Tag</c>:</p>
+ <table>
+ <row>
+ <cell><em>Tag</em></cell>
+ <cell><em>Info value type</em></cell>
+ </row>
+ <row>
+ <cell>select</cell>
+ <cell>select_ref()</cell>
+ </row>
+ <row>
+ <cell>abort</cell>
+ <cell>{select_ref(), Reason :: term()}</cell>
+ </row>
+ <tcaption>socket message info value type</tcaption>
+ </table>
+ <p>The <c>select_ref()</c> is the same as was received in the
+ <seetype marker="socket#select_info"><c>SelectInfo</c></seetype>. </p>
+ </section>
+ </section>
+
+ <section>
+ <marker id="socket_options"></marker>
+ <title>Socket Options</title>
+
+ <p>Options for level <c>otp</c>: </p>
+ <table>
+ <row>
+ <cell><em>Option Name</em></cell>
+ <cell><em>Value Type</em></cell>
+ <cell><em>Set</em></cell>
+ <cell><em>Get</em></cell>
+ <cell><em>Other Requirements and comments</em></cell>
+ </row>
+ <row>
+ <cell>assoc_id</cell>
+ <cell>integer()</cell>
+ <cell>no</cell>
+ <cell>yes</cell>
+ <cell>type = seqpacket, protocol = sctp, is an association</cell>
+ </row>
+ <row>
+ <cell>debug</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>iow</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>controlling_process</cell>
+ <cell>pid()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>rcvbuf</cell>
+ <cell>default | pos_integer() | {pos_integer(), pos_ineteger()}</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>'default' only valid for set.
+ The tuple form is only valid for type 'stream' and protocol 'tcp'.</cell>
+ </row>
+ <row>
+ <cell>rcvctrlbuf</cell>
+ <cell>default | pos_integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>default only valid for set</cell>
+ </row>
+ <row>
+ <cell>sndctrlbuf</cell>
+ <cell>default | pos_integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>default only valid for set</cell>
+ </row>
+ <row>
+ <cell>fd</cell>
+ <cell>integer()</cell>
+ <cell>no</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <tcaption>option levels</tcaption>
+ </table>
+
+ <p>Options for level <c>socket</c>: </p>
+ <table>
+ <row>
+ <cell><em>Option Name</em></cell>
+ <cell><em>Value Type</em></cell>
+ <cell><em>Set</em></cell>
+ <cell><em>Get</em></cell>
+ <cell><em>Other Requirements and comments</em></cell>
+ </row>
+ <row>
+ <cell>acceptconn</cell>
+ <cell>boolean()</cell>
+ <cell>no</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>bindtodevice</cell>
+ <cell>string()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>Before Linux 3.8, this socket option could be set, but not get.
+ Only works for some socket types (e.g. <c>inet</c>).
+ If empty value is set, the binding is removed.</cell>
+ </row>
+ <row>
+ <cell>broadcast</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = dgram</cell>
+ </row>
+ <row>
+ <cell>debug</cell>
+ <cell>integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>may require admin capability</cell>
+ </row>
+ <row>
+ <cell>domain</cell>
+ <cell>domain()</cell>
+ <cell>no</cell>
+ <cell>yes</cell>
+ <cell><em>Not</em> on FreeBSD (for instance)</cell>
+ </row>
+ <row>
+ <cell>dontroute</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>keepalive</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>linger</cell>
+ <cell>abort | linger()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>oobinline</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>peek_off</cell>
+ <cell>integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>domain = local (unix).
+ Currently disabled due to a possible infinite loop when
+ calling recv([peek]) the second time.
+ </cell>
+ </row>
+ <row>
+ <cell>priority</cell>
+ <cell>integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>protocol</cell>
+ <cell>protocol()</cell>
+ <cell>no</cell>
+ <cell>yes</cell>
+ <cell><em>Not</em> on (some) Darwin (for instance)</cell>
+ </row>
+ <row>
+ <cell>rcvbuf</cell>
+ <cell>non_neg_integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>rcvlowat</cell>
+ <cell>non_neg_integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>rcvtimeo</cell>
+ <cell>timeval()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>
+ This option is not normally supported (see why below).
+ OTP has to be explicitly built with the
+ <c>--enable-esock-rcvsndtime</c> configure option for this
+ to be available.
+ Since our implementation is <em>nonblocking</em>,
+ its unknown if and how this option works, or even if
+ it may cause malfunctions.
+ Therefor, we do not recommend setting this option.
+ Instead, use the <c>Timeout</c> argument to, for instance,
+ the
+ <seemfa marker="socket#recv/3"><c>recv/3</c></seemfa>
+ function.
+ </cell>
+ </row>
+ <row>
+ <cell>reuseaddr</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>reuseport</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>domain = inet | inet6</cell>
+ </row>
+ <row>
+ <cell>sndbuf</cell>
+ <cell>non_neg_integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>sndlowat</cell>
+ <cell>non_neg_integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>not changeable on Linux</cell>
+ </row>
+ <row>
+ <cell>sndtimeo</cell>
+ <cell>timeval()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>
+ This option is not normally supported (see why below).
+ OTP has to be explicitly built with the
+ <c>--enable-esock-rcvsndtime</c> configure option for this
+ to be available.
+ Since our implementation is <em>nonblocking</em>,
+ its unknown if and how this option works, or even if
+ it may cause malfunctions.
+ Therefor, we do not recommend setting this option.
+ Instead, use the <c>Timeout</c> argument to, for instance,
+ the
+ <seemfa marker="socket#send/3"><c>send/3</c></seemfa>
+ function.
+ </cell>
+ </row>
+ <row>
+ <cell>timestamp</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>type</cell>
+ <cell>type()</cell>
+ <cell>no</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <tcaption>socket options</tcaption>
+ </table>
+
+ <p>Options for level <c>ip</c>: </p>
+ <table>
+ <row>
+ <cell><em>Option Name</em></cell>
+ <cell><em>Value Type</em></cell>
+ <cell><em>Set</em></cell>
+ <cell><em>Get</em></cell>
+ <cell><em>Other Requirements and comments</em></cell>
+ </row>
+ <row>
+ <cell>add_membership</cell>
+ <cell>ip_mreq()</cell>
+ <cell>yes</cell>
+ <cell>no</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>add_source_membership</cell>
+ <cell>ip_mreq_source()</cell>
+ <cell>yes</cell>
+ <cell>no</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>block_source</cell>
+ <cell>ip_mreq_source()</cell>
+ <cell>yes</cell>
+ <cell>no</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>drop_membership</cell>
+ <cell>ip_mreq()</cell>
+ <cell>yes</cell>
+ <cell>no</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>drop_source_membership</cell>
+ <cell>ip_mreq_source()</cell>
+ <cell>yes</cell>
+ <cell>no</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>freebind</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>hdrincl</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = raw</cell>
+ </row>
+ <row>
+ <cell>minttl</cell>
+ <cell>integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = raw</cell>
+ </row>
+ <row>
+ <cell>msfilter</cell>
+ <cell>null | ip_msfilter()</cell>
+ <cell>yes</cell>
+ <cell>no</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>mtu</cell>
+ <cell>integer()</cell>
+ <cell>no</cell>
+ <cell>yes</cell>
+ <cell>type = raw</cell>
+ </row>
+ <row>
+ <cell>mtu_discover</cell>
+ <cell>ip_pmtudisc()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>multicast_all</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>multicast_if</cell>
+ <cell>any | ip4_address()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>multicast_loop</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>multicast_ttl</cell>
+ <cell>uint8()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>nodefrag</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = raw</cell>
+ </row>
+ <row>
+ <cell>pktinfo</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = dgram</cell>
+ </row>
+ <row>
+ <cell>recvdstaddr</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = dgram</cell>
+ </row>
+ <row>
+ <cell>recverr</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>recvif</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = dgram | raw</cell>
+ </row>
+ <row>
+ <cell>recvopts</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type =/= stream</cell>
+ </row>
+ <row>
+ <cell>recvorigdstaddr</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>recvttl</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type =/= stream</cell>
+ </row>
+ <row>
+ <cell>retopts</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type =/= stream</cell>
+ </row>
+ <row>
+ <cell>router_alert</cell>
+ <cell>integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = raw</cell>
+ </row>
+ <row>
+ <cell>sendsrcaddr</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>tos</cell>
+ <cell>ip_tos()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>some high-priority levels may require superuser capability</cell>
+ </row>
+ <row>
+ <cell>transparent</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>requires admin capability</cell>
+ </row>
+ <row>
+ <cell>ttl</cell>
+ <cell>integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>unblock_source</cell>
+ <cell>ip_mreq_source()</cell>
+ <cell>yes</cell>
+ <cell>no</cell>
+ <cell>none</cell>
+ </row>
+ <tcaption>ip options</tcaption>
+ </table>
+
+ <p>Options for level <c>ipv6</c>: </p>
+ <table>
+ <row>
+ <cell><em>Option Name</em></cell>
+ <cell><em>Value Type</em></cell>
+ <cell><em>Set</em></cell>
+ <cell><em>Get</em></cell>
+ <cell><em>Other Requirements and comments</em></cell>
+ </row>
+ <row>
+ <cell>addrform</cell>
+ <cell>inet</cell>
+ <cell>yes</cell>
+ <cell>no</cell>
+ <cell>allowed only for IPv6 sockets that are connected and bound to a
+ v4-mapped-on-v6 address</cell>
+ </row>
+ <row>
+ <cell>add_membership</cell>
+ <cell>ipv6_mreq()</cell>
+ <cell>yes</cell>
+ <cell>no</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>authhdr</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = dgram | raw, obsolete?</cell>
+ </row>
+ <row>
+ <cell>drop_membership</cell>
+ <cell>ipv6_mreq()</cell>
+ <cell>yes</cell>
+ <cell>no</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>dstopts</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = dgram | raw, requires superuser privileges to update</cell>
+ </row>
+ <row>
+ <cell>flowinfo</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = dgram | raw, requires superuser privileges to update</cell>
+ </row>
+ <row>
+ <cell>hoplimit</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = dgram | raw.
+ On some platforms (e.g. FreeBSD) is used to set in order to
+ get <c>hoplimit</c> as a control message heeader.
+ On others (e.g. Linux), <c>recvhoplimit</c> is set in order to get
+ <c>hoplimit</c>. </cell>
+ </row>
+ <row>
+ <cell>hopopts</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = dgram | raw, requires superuser privileges to update</cell>
+ </row>
+ <row>
+ <cell>mtu</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>Get: Only after the socket has been connected</cell>
+ </row>
+ <row>
+ <cell>mtu_discover</cell>
+ <cell>ipv6_pmtudisc()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>multicast_hops</cell>
+ <cell>default | uint8()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>multicast_if</cell>
+ <cell>integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = dgram | raw</cell>
+ </row>
+ <row>
+ <cell>multicast_loop</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>recverr</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>recvhoplimit</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = dgram | raw.
+ On some platforms (e.g. Linux), <c>recvhoplimit</c>
+ is set in order to get <c>hoplimit</c></cell>
+ </row>
+ <row>
+ <cell>recvpktinfo | pktinfo</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = dgram | raw.
+ On some platforms (e.g. FreeBSD) is used to set in order to
+ get <c>hoplimit</c> as a control message heeader.
+ On others (e.g. Linux), <c>recvhoplimit</c> is set in order to get
+ <c>hoplimit</c>. </cell>
+ </row>
+ <row>
+ <cell>recvtclass</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = dgram | raw.
+ On some platforms is used to set (=true) in order to
+ get the <c>tclass</c> control message heeader.
+ On others, <c>tclass</c> is set in order to get
+ <c>tclass</c> control message heeader. </cell>
+ </row>
+ <row>
+ <cell>router_alert</cell>
+ <cell>integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = raw</cell>
+ </row>
+ <row>
+ <cell>rthdr</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = dgram | raw, requires superuser privileges to update</cell>
+ </row>
+ <row>
+ <cell>tclass</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = dgram | raw.
+ On some platforms is used to set (=true) in order to
+ get the <c>tclass</c> control message heeader.
+ On others, <c>recvtclass</c> is set in order to get
+ <c>tclass</c> control message heeader. </cell>
+ </row>
+ <row>
+ <cell>unicast_hops</cell>
+ <cell>default | uint8()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>v6only</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>no</cell>
+ <cell>none</cell>
+ </row>
+ <tcaption>ipv6 options</tcaption>
+ </table>
+
+ <p>Options for level <c>tcp</c>: </p>
+ <table>
+ <row>
+ <cell><em>Option Name</em></cell>
+ <cell><em>Value Type</em></cell>
+ <cell><em>Set</em></cell>
+ <cell><em>Get</em></cell>
+ <cell><em>Other Requirements and comments</em></cell>
+ </row>
+ <row>
+ <cell>congestion</cell>
+ <cell>string()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>maxseg</cell>
+ <cell>integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>Set not allowed on all platforms.</cell>
+ </row>
+ <row>
+ <cell>nodelay</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <tcaption>tcp options</tcaption>
+ </table>
+
+ <p>Options for level <c>udp</c>: </p>
+ <table>
+ <row>
+ <cell><em>Option Name</em></cell>
+ <cell><em>Value Type</em></cell>
+ <cell><em>Set</em></cell>
+ <cell><em>Get</em></cell>
+ <cell><em>Other Requirements and comments</em></cell>
+ </row>
+ <row>
+ <cell>cork</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <tcaption>udp options</tcaption>
+ </table>
+
+ <p>Options for level <c>sctp</c>: </p>
+ <table>
+ <row>
+ <cell><em>Option Name</em></cell>
+ <cell><em>Value Type</em></cell>
+ <cell><em>Set</em></cell>
+ <cell><em>Get</em></cell>
+ <cell><em>Other Requirements and comments</em></cell>
+ </row>
+ <row>
+ <cell>associnfo</cell>
+ <cell>sctp_assocparams()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>autoclose</cell>
+ <cell>non_neg_integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>disable_fragments</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>events</cell>
+ <cell>sctp_event_subscribe()</cell>
+ <cell>yes</cell>
+ <cell>no</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>initmsg</cell>
+ <cell>sctp_initmsg()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>maxseg</cell>
+ <cell>non_neg_integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>nodelay</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>rtoinfo</cell>
+ <cell>sctp_rtoinfo()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <tcaption>sctp options</tcaption>
+ </table>
+
+ </section>
+</chapter>
+
diff --git a/lib/kernel/doc/src/specs.xml b/lib/kernel/doc/src/specs.xml
index 00f6f04218..990bc85782 100644
--- a/lib/kernel/doc/src/specs.xml
+++ b/lib/kernel/doc/src/specs.xml
@@ -35,6 +35,7 @@
<xi:include href="../specs/specs_pg2.xml"/>
<xi:include href="../specs/specs_rpc.xml"/>
<xi:include href="../specs/specs_seq_trace.xml"/>
+ <xi:include href="../specs/specs_socket.xml"/>
<xi:include href="../specs/specs_user.xml"/>
<xi:include href="../specs/specs_wrap_log_reader.xml"/>
<xi:include href="../specs/specs_zlib_stub.xml"/>
diff --git a/lib/kernel/src/Makefile b/lib/kernel/src/Makefile
index 07f4f2304c..6c75bcffee 100644
--- a/lib/kernel/src/Makefile
+++ b/lib/kernel/src/Makefile
@@ -135,6 +135,7 @@ MODULES = \
ram_file \
rpc \
seq_trace \
+ socket \
standard_error \
user \
user_drv \
diff --git a/lib/kernel/src/application_controller.erl b/lib/kernel/src/application_controller.erl
index 8db0646dae..869e9be1e8 100644
--- a/lib/kernel/src/application_controller.erl
+++ b/lib/kernel/src/application_controller.erl
@@ -271,13 +271,12 @@ which_applications(Timeout) ->
gen_server:call(?AC, which_applications, Timeout).
loaded_applications() ->
- ets:filter(ac_tab,
- fun([{{loaded, AppName}, #appl{descr = Descr, vsn = Vsn}}]) ->
- {true, {AppName, Descr, Vsn}};
- (_) ->
- false
- end,
- []).
+ ets:select(ac_tab,
+ [{
+ {{loaded, '$1'}, #appl{descr = '$2', vsn = '$3', _ = '_'}},
+ [],
+ [{{'$1', '$2', '$3'}}]
+ }]).
%% Returns some debug info
info() ->
diff --git a/lib/kernel/src/code.erl b/lib/kernel/src/code.erl
index 9eb0e80af4..7b0ef8cf38 100644
--- a/lib/kernel/src/code.erl
+++ b/lib/kernel/src/code.erl
@@ -862,7 +862,7 @@ get_doc_chunk(Filename, Mod) when is_atom(Mod) ->
{error,beam_lib,{file_error,_Filename,enoent}} ->
get_doc_chunk(Filename, atom_to_list(Mod));
{ok, {Mod, [{"Docs",Bin}]}} ->
- binary_to_term(Bin)
+ {ok,binary_to_term(Bin)}
end;
get_doc_chunk(Filename, Mod) ->
case filename:dirname(Filename) of
@@ -901,24 +901,22 @@ get_function_docs_from_ast(AST) ->
lists:flatmap(fun(E) -> get_function_docs_from_ast(E, AST) end, AST).
get_function_docs_from_ast({function,Anno,Name,Arity,_Code}, AST) ->
Signature = io_lib:format("~p/~p",[Name,Arity]),
- Specs = lists:filter(fun({attribute,_Ln,spec,{FA,_}}) ->
- case FA of
- {F,A} ->
- F =:= Name andalso A =:= Arity;
- {_, F, A} ->
- F =:= Name andalso A =:= Arity
- end;
- (_) -> false
- end, AST),
+ Specs = lists:filter(
+ fun({attribute,_Ln,spec,{FA,_}}) ->
+ case FA of
+ {F,A} ->
+ F =:= Name andalso A =:= Arity;
+ {_, F, A} ->
+ F =:= Name andalso A =:= Arity
+ end;
+ (_) -> false
+ end, AST),
SpecMd = case Specs of
- [S] -> #{ spec => [S] };
+ [S] -> #{ signature => [S] };
[] -> #{}
end,
- FnDocs = [],
- Md = SpecMd#{},
- [{{function, Name, Arity}, Anno, [unicode:characters_to_binary(Signature)],
- #{ <<"en">> => FnDocs },
- Md#{}}];
+ [{{function, Name, Arity}, Anno,
+ [unicode:characters_to_binary(Signature)], none, SpecMd}];
get_function_docs_from_ast(_, _) ->
[].
diff --git a/lib/kernel/src/dist_util.erl b/lib/kernel/src/dist_util.erl
index 0b43377821..75a48e8ac4 100644
--- a/lib/kernel/src/dist_util.erl
+++ b/lib/kernel/src/dist_util.erl
@@ -353,7 +353,7 @@ do_mark_pending(Kernel, MyNode, Node, Flags) ->
receive
{Kernel,{accept_pending,Ret}} ->
?trace("do_mark_pending(~p,~p,~p,~p) -> ~p~n",
- [Kernel,Node,Address,Flags,Ret]),
+ [Kernel, MyNode, Node, Flags, Ret]),
Ret
end.
@@ -674,7 +674,10 @@ send_name(#hs_data{socket = Socket, this_node = Node,
?ERL_DIST_VER_5;
is_integer(Version), Version >= ?ERL_DIST_VER_6 ->
- Creation = erts_internal:get_creation(),
+ Creation = case name_type(Flags) of
+ static -> erts_internal:get_creation();
+ dynamic -> 0
+ end,
NameLen = byte_size(NameBin),
?trace("send_name: 'N' node=~p creation=~w\n",
[Node, Creation]),
@@ -781,7 +784,7 @@ recv_name_new(HSData,
<<Creation:32>> = <<Cr3,Cr2,Cr1,Cr0>>,
<<NameLen:16>> = <<NL1,NL0>>,
{Name, _Residue} = lists:split(NameLen, Rest),
- ?trace("recv_name: 'N' node=~p creation=~w\n", [Node, Creation]),
+ ?trace("recv_name: 'N' name=~p creation=~w\n", [Name, Creation]),
case is_name_ok(Name, Flags) of
true ->
check_allowed(HSData, Name),
@@ -1135,7 +1138,7 @@ send_status(#hs_data{socket = Socket,
other_creation = Creation,
f_send = FSend},
named) ->
- ?debug({dist_util,self(),send_status, Node, Stat}),
+ ?debug({dist_util, self(), send_status, Node}),
NameBin = atom_to_binary(Node, utf8),
NameLen = byte_size(NameBin),
case FSend(Socket, [$s, "named:",
diff --git a/lib/kernel/src/gen_tcp.erl b/lib/kernel/src/gen_tcp.erl
index 0c98f62cdf..b44144d88a 100644
--- a/lib/kernel/src/gen_tcp.erl
+++ b/lib/kernel/src/gen_tcp.erl
@@ -130,7 +130,7 @@
{netns, file:filename_all()} |
{bind_to_device, binary()} |
option().
--type socket() :: port().
+-type socket() :: inet:socket().
-export_type([option/0, option_name/0, connect_option/0, listen_option/0,
socket/0, pktoptions_value/0]).
diff --git a/lib/kernel/src/gen_tcp_socket.erl b/lib/kernel/src/gen_tcp_socket.erl
index fe58cc0cba..68733ce8e6 100644
--- a/lib/kernel/src/gen_tcp_socket.erl
+++ b/lib/kernel/src/gen_tcp_socket.erl
@@ -291,10 +291,10 @@ send(?module_socket(Server, Socket), Data) ->
Result = socket_send(Socket, Data, SendTimeout),
send_result(Server, Meta, Result)
end;
- {ok, _Meta} ->
+ {ok, _BadMeta} ->
exit(badarg);
{error, _} = Error ->
- ?badarg_exit(Error)
+ Error
end.
%%
send_result(Server, Meta, Result) ->
@@ -1227,9 +1227,7 @@ handle_event(
{call, From}, {recv, Length, Timeout}, State, {P, D}) ->
case State of
'connected' ->
- handle_recv(
- P, recv_start(D, From, Length),
- [{{timeout, recv}, Timeout, recv}]);
+ handle_recv_start(P, D, From, Length, Timeout);
#recv{} ->
%% Receive in progress
{keep_state_and_data,
@@ -1352,6 +1350,32 @@ handle_connected(P, D, ActionsR) ->
handle_recv(P, recv_start(D), ActionsR)
end.
+handle_recv_start(
+ P, #{packet := Packet, buffer := Buffer} = D, From, Length, Timeout)
+ when Packet =:= raw, 0 < Length;
+ Packet =:= 0, 0 < Length ->
+ Size = iolist_size(Buffer),
+ if
+ Length =< Size ->
+ {Data, NewBuffer} =
+ split_binary(condense_buffer(Buffer), Length),
+ handle_recv_deliver(
+ P,
+ D#{recv_length => Length, % Redundant
+ recv_from => From,
+ buffer := NewBuffer},
+ [], Data);
+ true ->
+ N = Length - Size,
+ handle_recv(
+ P, D#{recv_length => N, recv_from => From},
+ [{{timeout, recv}, Timeout, recv}])
+ end;
+handle_recv_start(P, D, From, _Length, Timeout) ->
+ handle_recv(
+ P, D#{recv_length => 0, recv_from => From},
+ [{{timeout, recv}, Timeout, recv}]).
+
handle_recv(P, #{packet := Packet, recv_length := Length} = D, ActionsR) ->
if
0 < Length ->
@@ -1661,16 +1685,6 @@ cleanup_recv_reply(
end}.
%% Initialize packet recv state
-recv_start(#{packet := Packet} = D, From, Length) ->
- %%
- D#{recv_length =>
- case Packet of
- raw -> Length;
- 0 -> Length;
- _ -> 0
- end,
- recv_from => From}.
-
recv_start(D) ->
D#{recv_length => 0}.
@@ -1699,6 +1713,10 @@ recv_data_deliver(
[{reply, From, {ok, DeliverData}},
{{timeout, recv}, cancel}
| ActionsR]};
+ #{active := false} ->
+ D_1 = D#{buffer := unrecv_buffer(Data, maps:get(buffer, D))},
+ {recv_stop(next_packet(D_1, Packet, Data)),
+ ActionsR};
#{active := Active} ->
ModuleSocket = module_socket(P),
Owner !
@@ -1767,6 +1785,16 @@ catbin(Bin, <<>>) when is_binary(Bin) -> Bin;
catbin(Bin1, Bin2) when is_binary(Bin1), is_binary(Bin2) ->
<<Bin1/binary, Bin2/binary>>.
+unrecv_buffer(Data, Buffer) ->
+ case Buffer of
+ <<>> ->
+ Data;
+ _ when is_binary(Buffer) ->
+ [Data, Buffer];
+ _ ->
+ [Data | Buffer]
+ end.
+
condense_buffer([Bin]) when is_binary(Bin) -> Bin;
condense_buffer(Buffer) ->
iolist_to_binary(reverse_improper(Buffer, [])).
@@ -1993,7 +2021,7 @@ getstat_avg(SumTag, D, C, CntTag) ->
socket_info_counters(Socket) ->
#{counters := Counters} = socket:info(Socket),
- maps:from_list(Counters).
+ Counters.
receive_counter_wrap(Socket, D, Wrapped) ->
receive
diff --git a/lib/kernel/src/inet.erl b/lib/kernel/src/inet.erl
index 8b944a1049..6ae5351652 100644
--- a/lib/kernel/src/inet.erl
+++ b/lib/kernel/src/inet.erl
@@ -128,7 +128,8 @@
'etimedout' |
'ewouldblock' |
'exbadport' | 'exbadseq' | file:posix().
--type socket() :: port().
+-type module_socket() :: {'$inet', Handler :: module(), Handle :: term()}.
+-type socket() :: port() | module_socket().
-type socket_setopt() ::
gen_sctp:option() | gen_tcp:option() | gen_udp:option().
@@ -1433,7 +1434,7 @@ gethostbyaddr_tm_native(Addr, Timer, Opts) ->
Family :: address_family(),
Type :: socket_type(),
Module :: atom()) ->
- {'ok', socket()} | {'error', posix()}.
+ {'ok', port()} | {'error', posix()}.
open(FdO, Addr, Port, Opts, Protocol, Family, Type, Module)
when is_integer(FdO), FdO < 0;
diff --git a/lib/kernel/src/inet6_udp.erl b/lib/kernel/src/inet6_udp.erl
index 7ebdb3d301..b8c33e91ec 100644
--- a/lib/kernel/src/inet6_udp.erl
+++ b/lib/kernel/src/inet6_udp.erl
@@ -44,10 +44,10 @@ getaddr(Address, Timer) -> inet:getaddr(Address, ?FAMILY, Timer).
%% inet_udp special this side addresses
translate_ip(IP) -> inet:translate_ip(IP, ?FAMILY).
--spec open(_) -> {ok, inet:socket()} | {error, atom()}.
+-spec open(_) -> {ok, port()} | {error, atom()}.
open(Port) -> open(Port, []).
--spec open(_, _) -> {ok, inet:socket()} | {error, atom()}.
+-spec open(_, _) -> {ok, port()} | {error, atom()}.
open(Port, Opts) ->
case inet:udp_options(
[{port,Port} | Opts],
@@ -91,7 +91,7 @@ recv(S, Len) ->
recv(S, Len, Time) ->
prim_inet:recvfrom(S, Len, Time).
--spec close(inet:socket()) -> ok.
+-spec close(port()) -> ok.
close(S) ->
inet:udp_close(S).
diff --git a/lib/kernel/src/inet_udp.erl b/lib/kernel/src/inet_udp.erl
index ace095f30c..00ebd4bb1d 100644
--- a/lib/kernel/src/inet_udp.erl
+++ b/lib/kernel/src/inet_udp.erl
@@ -45,10 +45,10 @@ getaddr(Address, Timer) -> inet:getaddr(Address, ?FAMILY, Timer).
%% inet_udp special this side addresses
translate_ip(IP) -> inet:translate_ip(IP, ?FAMILY).
--spec open(_) -> {ok, inet:socket()} | {error, atom()}.
+-spec open(_) -> {ok, port()} | {error, atom()}.
open(Port) -> open(Port, []).
--spec open(_, _) -> {ok, inet:socket()} | {error, atom()}.
+-spec open(_, _) -> {ok, port()} | {error, atom()}.
open(Port, Opts) ->
case inet:udp_options(
[{port,Port}, {recbuf, ?RECBUF} | Opts],
@@ -92,7 +92,7 @@ recv(S, Len) ->
recv(S, Len, Time) ->
prim_inet:recvfrom(S, Len, Time).
--spec close(inet:socket()) -> ok.
+-spec close(port()) -> ok.
close(S) ->
inet:udp_close(S).
diff --git a/lib/kernel/src/kernel.app.src b/lib/kernel/src/kernel.app.src
index 48c77c49a9..e9f6049d5f 100644
--- a/lib/kernel/src/kernel.app.src
+++ b/lib/kernel/src/kernel.app.src
@@ -115,6 +115,7 @@
raw_file_io_list,
raw_file_io_raw,
seq_trace,
+ socket,
standard_error,
wrap_log_reader]},
{registered, [application_controller,
@@ -154,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/lib/kernel/src/kernel.appup.src b/lib/kernel/src/kernel.appup.src
index f42dd8ca6e..09a55d6f0a 100644
--- a/lib/kernel/src/kernel.appup.src
+++ b/lib/kernel/src/kernel.appup.src
@@ -21,6 +21,7 @@
%% versions from the following OTP releases:
%% - OTP 21
%% - OTP 22
+%% - OTP 23
%%
%% We also allow upgrade from, and downgrade to all
%% versions that have branched off from the above
@@ -44,7 +45,8 @@
{<<"^6\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^6\\.5$">>,[restart_new_emulator]},
{<<"^6\\.5\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^6\\.5\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}],
+ {<<"^6\\.5\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^6\\.5\\.2(?:\\.[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]},
@@ -62,4 +64,5 @@
{<<"^6\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^6\\.5$">>,[restart_new_emulator]},
{<<"^6\\.5\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^6\\.5\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}]}.
+ {<<"^6\\.5\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^6\\.5\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]}]}.
diff --git a/lib/kernel/src/net.erl b/lib/kernel/src/net.erl
index aceb903bb6..905114ab7c 100644
--- a/lib/kernel/src/net.erl
+++ b/lib/kernel/src/net.erl
@@ -152,7 +152,6 @@ relay(X) -> slave:relay(X).
info() ->
prim_net:info().
-else.
--dialyzer({nowarn_function, info/0}).
info() ->
erlang:error(notsup).
-endif.
@@ -164,7 +163,6 @@ info() ->
command(Cmd) ->
prim_net:command(Cmd).
-else.
--dialyzer({nowarn_function, command/1}).
command(_Cmd) ->
erlang:error(notsup).
-endif.
@@ -191,7 +189,6 @@ command(_Cmd) ->
gethostname() ->
prim_net:gethostname().
-else.
--dialyzer({nowarn_function, gethostname/0}).
gethostname() ->
erlang:error(notsup).
-endif.
@@ -208,6 +205,8 @@ gethostname() ->
Info :: name_info(),
Reason :: term().
+-dialyzer({no_return, getnameinfo/1}).
+
getnameinfo(SockAddr) ->
getnameinfo(SockAddr, undefined).
@@ -226,25 +225,18 @@ getnameinfo(SockAddr) ->
-endif.
-ifdef(USE_ESOCK).
-getnameinfo(SockAddr, [] = _Flags) ->
- getnameinfo(SockAddr, undefined);
-getnameinfo(#{family := Fam, addr := _Addr} = SockAddr, Flags)
- when ((Fam =:= inet) orelse (Fam =:= inet6)) andalso
- (is_list(Flags) orelse (Flags =:= undefined)) ->
- prim_net:getnameinfo(socket:ensure_sockaddr(SockAddr), Flags);
-getnameinfo(#{family := Fam, path := _Path} = SockAddr, Flags)
- when (Fam =:= local) andalso (is_list(Flags) orelse (Flags =:= undefined)) ->
+getnameinfo(SockAddr, Flags)
+ when is_map(SockAddr), is_list(Flags);
+ is_map(SockAddr), Flags =:= undefined ->
prim_net:getnameinfo(SockAddr, Flags).
+
-else.
--dialyzer({nowarn_function, getnameinfo/2}).
-getnameinfo(SockAddr, [] = _Flags) ->
- getnameinfo(SockAddr, undefined);
-getnameinfo(#{family := Fam, addr := _Addr} = _SockAddr, Flags)
- when ((Fam =:= inet) orelse (Fam =:= inet6)) andalso
- (is_list(Flags) orelse (Flags =:= undefined)) ->
- erlang:error(notsup);
-getnameinfo(#{family := Fam, path := _Path} = _SockAddr, Flags)
- when (Fam =:= local) andalso (is_list(Flags) orelse (Flags =:= undefined)) ->
+
+-dialyzer({no_return, getnameinfo/2}).
+
+getnameinfo(SockAddr, Flags)
+ when is_map(SockAddr), is_list(Flags);
+ is_map(SockAddr), Flags =:= undefined ->
erlang:error(notsup).
-endif.
@@ -260,6 +252,8 @@ getnameinfo(#{family := Fam, path := _Path} = _SockAddr, Flags)
Info :: [address_info()],
Reason :: term().
+-dialyzer({no_return, getaddrinfo/1}).
+
getaddrinfo(Host) when is_list(Host) ->
getaddrinfo(Host, undefined).
@@ -285,7 +279,6 @@ getaddrinfo(Host, Service)
(not ((Service =:= undefined) andalso (Host =:= undefined))) ->
prim_net:getaddrinfo(Host, Service).
-else.
--dialyzer({nowarn_function, getaddrinfo/2}).
getaddrinfo(Host, Service)
when (is_list(Host) orelse (Host =:= undefined)) andalso
(is_list(Service) orelse (Service =:= undefined)) andalso
@@ -332,7 +325,6 @@ getifaddrs(Filter) when is_function(Filter, 1) ->
getifaddrs(Namespace) when is_list(Namespace) ->
prim_net:getifaddrs(#{netns => Namespace}).
-else.
--dialyzer({nowarn_function, getifaddrs/1}).
getifaddrs(Filter) when is_atom(Filter) orelse
is_map(Filter) orelse
is_function(Filter) ->
@@ -348,6 +340,8 @@ getifaddrs(Namespace) when is_list(Namespace) ->
IfAddrs :: [ifaddrs()],
Reason :: term().
+-dialyzer({no_return, getifaddrs/2}).
+
getifaddrs(Filter, Namespace)
when (is_atom(Filter) orelse is_map(Filter)) andalso is_list(Namespace) ->
do_getifaddrs(getifaddrs_filter_map(Filter),
@@ -356,6 +350,8 @@ getifaddrs(Filter, Namespace)
when is_function(Filter, 1) andalso is_list(Namespace) ->
do_getifaddrs(Filter, fun() -> getifaddrs(Namespace) end).
+-dialyzer({no_return, do_getifaddrs/2}).
+
do_getifaddrs(Filter, GetIfAddrs) ->
case GetIfAddrs() of
{ok, IfAddrs0} when is_function(Filter) ->
@@ -395,6 +391,8 @@ getifaddrs_filter_map_inet6() ->
getifaddrs_filter_map_packet() ->
#{family => packet, flags => any}.
+-compile({nowarn_unused_function, getifaddrs_filter/2}).
+
getifaddrs_filter(#{family := FFamily, flags := FFlags},
#{addr := #{family := Family}, flags := Flags} = _Entry)
when (FFamily =:= default) andalso
@@ -419,6 +417,8 @@ getifaddrs_filter(#{family := FFamily, flags := FFlags},
getifaddrs_filter(_Filter, _Entry) ->
false.
+-compile({nowarn_unused_function, getifaddrs_filter_flags/2}).
+
getifaddrs_filter_flags(any, _Flags) ->
true;
getifaddrs_filter_flags(FilterFlags, Flags) ->
@@ -442,7 +442,6 @@ getifaddrs_filter_flags(FilterFlags, Flags) ->
if_name2index(If) when is_list(If) ->
prim_net:if_name2index(If).
-else.
--dialyzer({nowarn_function, if_name2index/1}).
if_name2index(If) when is_list(If) ->
erlang:error(notsup).
-endif.
@@ -465,7 +464,6 @@ if_name2index(If) when is_list(If) ->
if_index2name(Idx) when is_integer(Idx) ->
prim_net:if_index2name(Idx).
-else.
--dialyzer({nowarn_function, if_index2name/1}).
if_index2name(Idx) when is_integer(Idx) ->
erlang:error(notsup).
-endif.
@@ -488,7 +486,6 @@ if_index2name(Idx) when is_integer(Idx) ->
if_names() ->
prim_net:if_names().
-else.
--dialyzer({nowarn_function, if_names/0}).
if_names() ->
erlang:error(notsup).
-endif.
diff --git a/lib/kernel/src/net_kernel.erl b/lib/kernel/src/net_kernel.erl
index c93862e602..b0e6127520 100644
--- a/lib/kernel/src/net_kernel.erl
+++ b/lib/kernel/src/net_kernel.erl
@@ -1679,14 +1679,18 @@ epmd_module() ->
%% dist_listen() -> whether the erlang distribution should listen for connections
%%
dist_listen() ->
- case init:get_argument(dist_listen) of
- {ok,[[DoListen]]} ->
- list_to_atom(DoListen) =/= false;
- _ ->
- true
+ case persistent_term:get(net_kernel, undefined) of
+ dynamic_node_name ->
+ false;
+ _ ->
+ case init:get_argument(dist_listen) of
+ {ok,[[DoListen]]} ->
+ list_to_atom(DoListen) =/= false;
+ _ ->
+ true
+ end
end.
-
%%
%% Start all protocols
%%
@@ -1761,8 +1765,12 @@ wrap_creation(Cr) ->
start_protos_listen(Node, Ps, CleanHalt) ->
- {Name, "@"++Host} = split_node(Node),
- start_protos_listen(list_to_atom(Name), Host, Node, Ps, [], CleanHalt).
+ case split_node(Node) of
+ {"undefined", _} ->
+ start_protos_no_listen(Node, Ps, [], CleanHalt);
+ {Name, "@"++Host} ->
+ start_protos_listen(list_to_atom(Name), Host, Node, Ps, [], CleanHalt)
+ end.
start_protos_listen(Name, Host, Node, [Proto | Ps], Ls, CleanHalt) ->
Mod = list_to_atom(Proto ++ "_dist"),
try try Mod:listen(Name,Host)
diff --git a/lib/kernel/src/socket.erl b/lib/kernel/src/socket.erl
new file mode 100644
index 0000000000..9533066c6b
--- /dev/null
+++ b/lib/kernel/src/socket.erl
@@ -0,0 +1,2570 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 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%
+%%
+
+-module(socket).
+
+-compile({no_auto_import,[error/1]}).
+
+%% Administrative and "global" utility functions
+-export([
+ number_of/0,
+ which_sockets/0, which_sockets/1,
+
+ debug/1, socket_debug/1,
+ info/0, info/1,
+ supports/0, supports/1, supports/2,
+ is_supported/1, is_supported/2, is_supported/3
+ ]).
+
+-export([
+ open/1, open/2, open/3, open/4,
+ bind/2, bind/3,
+ connect/1, connect/2, connect/3,
+ listen/1, listen/2,
+ accept/1, accept/2,
+
+ send/2, send/3, send/4,
+ sendto/3, sendto/4, sendto/5,
+ sendmsg/2, sendmsg/3, sendmsg/4,
+
+ recv/1, recv/2, recv/3, recv/4,
+ recvfrom/1, recvfrom/2, recvfrom/3, recvfrom/4,
+ recvmsg/1, recvmsg/2, recvmsg/3, recvmsg/5,
+
+ close/1,
+ shutdown/2,
+
+ setopt/4,
+ getopt/3,
+
+ sockname/1,
+ peername/1,
+
+ cancel/2
+ ]).
+
+-export_type([
+ socket/0,
+
+ select_tag/0,
+ select_ref/0,
+ select_info/0,
+
+ socket_counters/0,
+ socket_info/0,
+
+ domain/0,
+ type/0,
+ protocol/0,
+
+ port_number/0,
+ ip4_address/0,
+ ip6_address/0,
+ sockaddr/0,
+ sockaddr_in4/0,
+ sockaddr_in6/0,
+ sockaddr_un/0,
+ sockaddr_ll/0,
+
+ send_flags/0,
+ send_flag/0,
+
+ recv_flags/0,
+ recv_flag/0,
+
+ shutdown_how/0,
+
+ sockopt_level/0,
+ otp_socket_option/0,
+ socket_option/0,
+ ip_socket_option/0,
+ ipv6_socket_option/0,
+ tcp_socket_option/0,
+ udp_socket_option/0,
+ sctp_socket_option/0,
+ raw_socket_option/0,
+
+ timeval/0,
+ ip_tos/0,
+ ip_mreq/0,
+ ip_mreq_source/0,
+ ip_pmtudisc/0,
+ ip_msfilter_mode/0,
+ ip_msfilter/0,
+ ip_pktinfo/0,
+ ipv6_mreq/0,
+ ipv6_pmtudisc/0,
+ ipv6_pktinfo/0,
+ in6_flow_info/0,
+ in6_scope_id/0,
+ sctp_assoc_id/0,
+ sctp_sndrcvinfo/0,
+ sctp_event_subscribe/0,
+ sctp_assocparams/0,
+ sctp_initmsg/0,
+ sctp_rtoinfo/0,
+
+
+ msghdr_flag/0,
+ msghdr_flags/0,
+ msghdr/0,
+ cmsghdr_level/0,
+ cmsghdr_type/0,
+ %% cmsghdr_data/0,
+ cmsghdr_recv/0, cmsghdr_send/0,
+
+ ee_origin/0,
+ icmp_dest_unreach/0,
+ icmpv6_dest_unreach/0,
+ extended_err/0,
+
+ uint8/0,
+ uint16/0,
+ uint20/0,
+ uint32/0,
+ int32/0
+ ]).
+
+%% Also in prim_socket
+-define(REGISTRY, socket_registry).
+
+
+-type socket_counters() :: #{read_byte := non_neg_integer(),
+ read_fails := non_neg_integer(),
+ read_pkg := non_neg_integer(),
+ read_pkg_max := non_neg_integer(),
+ read_tries := non_neg_integer(),
+ read_waits := non_neg_integer(),
+ write_byte := non_neg_integer(),
+ write_fails := non_neg_integer(),
+ write_pkg := non_neg_integer(),
+ write_pkg_max := non_neg_integer(),
+ write_tries := non_neg_integer(),
+ write_waits := non_neg_integer(),
+ acc_success := non_neg_integer(),
+ acc_fails := non_neg_integer(),
+ acc_tries := non_neg_integer(),
+ acc_waits := non_neg_integer()}.
+-type socket_info() :: #{domain := domain(),
+ type := type(),
+ protocol := protocol(),
+ ctrl := pid(),
+ ctype := normal | fromfd | {fromfd, integer()},
+ counters := socket_counters(),
+ num_readers := non_neg_integer(),
+ num_writers := non_neg_integer(),
+ num_acceptors := non_neg_integer(),
+ writable := boolean(),
+ readable := boolean()}.
+
+-type uint8() :: 0..16#FF.
+-type uint16() :: 0..16#FFFF.
+-type uint20() :: 0..16#FFFFF.
+-type uint32() :: 0..16#FFFFFFFF.
+-type int32() :: -2147483648..2147483647.
+
+
+%% We support only a subset of all domains.
+-type domain() :: local | inet | inet6.
+
+%% We support only a subset of all types.
+%% RDM - Reliably Delivered Messages
+-type type() :: stream | dgram | raw | rdm | seqpacket.
+
+%% We support only a subset of all protocols:
+%% Note that the '{raw, integer()}' construct is intended
+%% to be used with type = raw.
+%% Note also that only the "superuser" can create a raw socket.
+-type protocol() :: ip | tcp | udp | sctp | icmp | igmp | {raw, integer()}.
+
+-type port_number() :: 0..65535.
+
+-type ip4_address() :: {0..255, 0..255, 0..255, 0..255}.
+
+-type in6_flow_info() :: uint20().
+-type in6_scope_id() :: uint32().
+
+-type ip6_address() ::
+ {0..65535,
+ 0..65535,
+ 0..65535,
+ 0..65535,
+ 0..65535,
+ 0..65535,
+ 0..65535,
+ 0..65535}.
+
+-type timeval() :: #{sec := integer(),
+ usec := integer()}.
+
+-type ip_pktinfo() :: #{
+ ifindex := non_neg_integer(), % Interface Index
+ spec_dst := ip4_address(), % Local Address
+ addr := ip4_address() % Header Destination address
+ }.
+
+%% If the integer value is used, its up to the caller to ensure its valid!
+-type ip_tos() :: lowdelay |
+ throughput |
+ reliability |
+ mincost |
+ integer().
+
+%% This type is used when requesting to become member of a multicast
+%% group with a call to setopt. Example:
+%%
+%% socket:setopt(Socket, ip, add_membership, #{multiaddr => Addr,
+%% interface => any}).
+%%
+%% Its also used when removing from a multicast group. Example:
+%%
+%% socket:setopt(Socket, ip, drop_membership, #{multiaddr => Addr,
+%% interface => any}).
+%%
+
+-type ip_mreq() :: #{multiaddr := ip4_address(),
+ interface := any | ip4_address()}.
+%% -type ip_mreqn() :: #{multiaddr := ip4_address(),
+%% address := any | ip4_address(),
+%% ifindex := integer()}.
+
+-type ip_mreq_source() :: #{multiaddr := ip4_address(),
+ interface := ip4_address(),
+ sourceaddr := ip4_address()}.
+
+-type ip_pmtudisc() :: want | dont | do | probe.
+
+%% multiaddr: Multicast group address
+%% interface: Address of local interface
+%% mode: Filter mode
+%% slist: List of source addresses
+-type ip_msfilter_mode() :: include | exclude.
+
+-type ip_msfilter() :: #{multiaddr := ip4_address(),
+ interface := ip4_address(),
+ mode := ip_msfilter_mode(),
+ slist := [ip4_address()]}.
+
+-type ipv6_mreq() :: #{multiaddr := ip6_address(),
+ interface := non_neg_integer()}.
+
+-type ipv6_pmtudisc() :: ip_pmtudisc().
+
+-type ipv6_pktinfo() :: #{
+ addr := ip6_address(),
+ ifindex := integer()
+ }.
+
+
+-type sctp_assoc_id() :: int32().
+-type sctp_sndrcvinfo() :: #{
+ stream := uint16(),
+ ssn := uint16(),
+ flags := uint16(),
+ ppid := uint16(),
+ context := uint16(),
+ timetolive := uint16(),
+ tsn := uint16(),
+ cumtsn := uint16(),
+ assoc_id := sctp_assoc_id()
+ }.
+
+-type sctp_event_subscribe() :: #{data_in := boolean(),
+ association := boolean(),
+ address := boolean(),
+ send_failure := boolean(),
+ peer_error := boolean(),
+ shutdown := boolean(),
+ partial_delivery := boolean(),
+ adaptation_layer := boolean(),
+ authentication := boolean(),
+ sender_dry := boolean()}.
+
+-type sctp_assocparams() :: #{assoc_id := sctp_assoc_id(),
+ max_rxt := uint16(),
+ num_peer_dests := uint16(),
+ peer_rwnd := uint32(),
+ local_rwnd := uint32(),
+ cookie_life := uint32()}.
+
+-type sctp_initmsg() :: #{num_outstreams := uint16(),
+ max_instreams := uint16(),
+ max_attempts := uint16(),
+ max_init_timeo := uint16()
+ }.
+
+-type sctp_rtoinfo() :: #{assoc_id := sctp_assoc_id(),
+ initial := uint32(),
+ max := uint32(),
+ min := uint32()}.
+
+-type sockaddr_un() :: #{family := local,
+ path := binary() | string()}.
+-type sockaddr_in4() :: #{family := inet,
+ port := port_number(),
+ %% The 'broadcast' here is the "limited broadcast"
+ addr := any | broadcast | loopback | ip4_address()}.
+-type sockaddr_in6() :: #{family := inet6,
+ port := port_number(),
+ addr := any | loopback | ip6_address(),
+ flowinfo := in6_flow_info(),
+ scope_id := in6_scope_id()}.
+-type sockaddr_ll() :: #{family := packet,
+ protocol := non_neg_integer(),
+ ifindex := integer(),
+ pkttype := packet_type(),
+ hatype := non_neg_integer(),
+ addr := binary()}.
+-type packet_type() :: host | broadcast | multicast | otherhost |
+ outgoing | loopback | user | kernel | fastroute |
+ non_neg_integer().
+-type sockaddr() :: sockaddr_in4() |
+ sockaddr_in6() |
+ sockaddr_un() |
+ sockaddr_ll().
+
+%% otp - This option is internal to our (OTP) implementation.
+%% socket - The socket layer (SOL_SOCKET).
+%% ip - The IP layer (SOL_IP or is it IPPROTO_IP?).
+%% ipv6 - The IPv6 layer (SOL_IPV6).
+%% tcp - The TCP (Transport Control Protocol) layer (IPPROTO_TCP).
+%% udp - The UDP (User Datagram Protocol) layer (IPPROTO_UDP).
+%% sctp - The SCTP (Stream Control Transmission Protocol) layer (IPPROTO_SCTP).
+%% Int - Raw level, sent down and used "as is".
+%% Its up to the caller to make sure this is correct!
+-type sockopt_level() :: otp |
+ socket |
+ ip | ipv6 | tcp | udp | sctp |
+ non_neg_integer().
+
+%% There are some options that are 'read-only'.
+%% Should those be included here or in a special list?
+%% Should we just document it and leave it to the user?
+%% Or catch it in the encode functions?
+%% A setopt for a readonly option leads to einval?
+%% Do we really need a sndbuf?
+
+-type otp_socket_option() :: debug |
+ iow |
+ controlling_process |
+ rcvbuf | % sndbuf |
+ rcvctrlbuf |
+ sndctrlbuf |
+ meta |
+ fd.
+%% Shall we have special treatment of linger??
+%% read-only options:
+%% domain | protocol | type.
+%% FreeBSD (only?): acceptfilter
+-type socket_option() :: acceptconn |
+ acceptfilter |
+ bindtodevice |
+ broadcast |
+ busy_poll |
+ debug |
+ domain |
+ dontroute |
+ error |
+ keepalive |
+ linger |
+ mark |
+ oobinline |
+ passcred |
+ peek_off |
+ peercred |
+ priority |
+ protocol |
+ rcvbuf |
+ rcvbufforce |
+ rcvlowat |
+ rcvtimeo |
+ reuseaddr |
+ reuseport |
+ rxq_ovfl |
+ setfib |
+ sndbuf |
+ sndbufforce |
+ sndlowat |
+ sndtimeo |
+ timestamp |
+ type.
+
+%% Read-only options:
+%% mtu
+%%
+%% Options only valid on FreeBSD?:
+%% dontfrag
+%% Options only valid for RAW sockets:
+%% nodefrag (linux only?)
+-type ip_socket_option() :: add_membership |
+ add_source_membership |
+ block_source |
+ dontfrag |
+ drop_membership |
+ drop_source_membership |
+ freebind |
+ hdrincl |
+ minttl |
+ msfilter |
+ mtu |
+ mtu_discover |
+ multicast_all |
+ multicast_if |
+ multicast_loop |
+ multicast_ttl |
+ nodefrag |
+ options |
+ pktinfo |
+ recverr |
+ recvif |
+ recvdstaddr |
+ recvopts |
+ recvorigdstaddr |
+ recvtos |
+ recvttl |
+ retopts |
+ router_alert |
+ sndsrcaddr |
+ tos |
+ transparent |
+ ttl |
+ unblock_source.
+-type ipv6_socket_option() ::
+ addrform |
+ add_membership |
+ authhdr |
+ auth_level |
+ checksum |
+ drop_membership |
+ dstopts |
+ esp_trans_level |
+ esp_network_level |
+ faith |
+ flowinfo |
+ hopopts |
+ ipcomp_level |
+ join_group |
+ leave_group |
+ mtu |
+ mtu_discover |
+ multicast_hops |
+ multicast_if |
+ multicast_loop |
+ portrange |
+ pktoptions |
+ recverr |
+ recvhoplimit | hoplimit |
+ recvpktinfo | pktinfo |
+ recvtclass |
+ router_alert |
+ rthdr |
+ tclass |
+ unicast_hops |
+ use_min_mtu |
+ v6only.
+
+-type tcp_socket_option() :: congestion |
+ cork |
+ info |
+ keepcnt |
+ keepidle |
+ keepintvl |
+ maxseg |
+ md5sig |
+ nodelay |
+ noopt |
+ nopush |
+ syncnt |
+ user_timeout.
+
+-type udp_socket_option() :: cork.
+
+-type sctp_socket_option() ::
+ adaption_layer |
+ associnfo |
+ auth_active_key |
+ auth_asconf |
+ auth_chunk |
+ auth_key |
+ auth_delete_key |
+ autoclose |
+ context |
+ default_send_params |
+ delayed_ack_time |
+ disable_fragments |
+ hmac_ident |
+ events |
+ explicit_eor |
+ fragment_interleave |
+ get_peer_addr_info |
+ initmsg |
+ i_want_mapped_v4_addr |
+ local_auth_chunks |
+ maxseg |
+ maxburst |
+ nodelay |
+ partial_delivery_point |
+ peer_addr_params |
+ peer_auth_chunks |
+ primary_addr |
+ reset_streams |
+ rtoinfo |
+ set_peer_primary_addr |
+ status |
+ use_ext_recvinfo.
+
+-type raw_socket_option() :: filter.
+
+%% -type plain_socket_option() :: integer().
+%% -type sockopt() :: otp_socket_option() |
+%% socket_option() |
+%% ip_socket_option() |
+%% ipv6_socket_option() |
+%% tcp_socket_option() |
+%% udp_socket_option() |
+%% sctp_socket_option() |
+%% raw_socket_option() |
+%% plain_socket_option().
+
+%% The names of these macros match the names of corresponding
+%%C functions in the NIF code, so a search will match both
+%%
+-define(socket_tag, '$socket').
+%%
+%% Our socket abstract data type
+-define(mk_socket(Ref), {?socket_tag, (Ref)}).
+%%
+%% Messages sent from the nif-code to erlang processes:
+-define(mk_socket_msg(Socket, Tag, Info), {?socket_tag, (Socket), (Tag), (Info)}).
+
+-opaque socket() :: ?mk_socket(reference()).
+
+-type send_flags() :: [send_flag()].
+-type send_flag() :: confirm |
+ dontroute |
+ eor |
+ more |
+ nosignal |
+ oob.
+
+%% Note that not all of these flags are useful for every recv function!
+%%
+-type recv_flags() :: [recv_flag()].
+-type recv_flag() :: cmsg_cloexec |
+ errqueue |
+ oob |
+ peek |
+ trunc.
+
+-type shutdown_how() :: read | write | read_write.
+
+-type msghdr_flag() :: ctrunc | eor | errqueue | oob | trunc.
+-type msghdr_flags() :: [msghdr_flag()].
+-type msghdr() :: #{
+ %% *Optional* target address
+ %% Used on an unconnected socket to specify the
+ %% target address for a datagram.
+ addr := sockaddr(),
+
+ iov := [binary()],
+
+ %% The maximum size of the control buffer is platform
+ %% specific. It is the users responsibility to ensure
+ %% that its not exceeded.
+ ctrl := [cmsghdr_recv()] | [cmsghdr_send()],
+
+ %% Only valid with recvmsg
+ flags := msghdr_flags()
+ }.
+%% We are able to (completely) decode *some* control message headers.
+%% Even if we are able to decode both level and type, we may not be
+%% able to decode the data, in which case it will be a binary.
+
+-type cmsghdr_level() :: socket | ip | ipv6 | integer().
+-type cmsghdr_type() :: credentials |
+ hoplevel |
+ origdstaddr |
+ pktinfo |
+ recvtos |
+ rights |
+ timestamp |
+ tos |
+ ttl |
+ integer().
+-type cmsghdr_recv() ::
+ #{level := socket, type := timestamp, data := timeval()} |
+ #{level := socket, type := rights, data := binary()} |
+ #{level := socket, type := credentials, data := binary()} |
+ #{level := socket, type := integer(), data := binary()} |
+ #{level := ip, type := tos, data := ip_tos()} |
+ #{level := ip, type := recvtos, data := ip_tos()} |
+ #{level := ip, type := ttl, data := integer()} |
+ #{level := ip, type := recvttl, data := integer()} |
+ #{level := ip, type := pktinfo, data := ip_pktinfo()} |
+ #{level := ip, type := origdstaddr, data := sockaddr_in4()} |
+ #{level := ip, type := recverr, data := extended_err() | binary()} |
+ #{level := ip, type := integer(), data := binary()} |
+ #{level := ipv6, type := hoplevel, data := integer()} |
+ #{level := ipv6, type := pktinfo, data := ipv6_pktinfo()} |
+ #{level := ipv6, type := recverr, data := extended_err() | binary()} |
+ #{level := ipv6, type := tclass, data := integer()} |
+ #{level := ipv6, type := integer(), data := binary()} |
+ #{level := integer(), type := integer(), data := binary()}.
+-type cmsghdr_send() ::
+ #{level := socket, type := timestamp, data := binary()} |
+ #{level := socket, type := rights, data := binary()} |
+ #{level := socket, type := credentials, data := binary()} |
+ #{level := socket, type := integer(), data := binary()} |
+ #{level := ip, type := tos, data := ip_tos() | binary()} |
+ #{level := ip, type := ttl, data := integer() | binary()} |
+ #{level := ip, type := integer(), data := binary()} |
+ #{level := ipv6, type := tclass, data := integer()} |
+ #{level := ipv6, type := integer(), data := binary()} |
+ #{level := udp, type := integer(), data := binary()} |
+ #{level := integer(), type := integer(), data := binary()}.
+
+-type ee_origin() :: none | local | icmp | icmp6 | uint8().
+-type icmp_dest_unreach() :: net_unreach | host_unreach | port_unreach | frag_needed |
+ net_unknown | host_unknown | uint8().
+-type icmpv6_dest_unreach() :: noroute | adm_prohibited | not_neighbour | addr_unreach |
+ port_unreach | policy_fail | reject_route | uint8().
+-type extended_err() ::
+ #{error := term(),
+ origin := icmp,
+ type := dest_unreach,
+ code := icmp_dest_unreach(),
+ info := uint32(),
+ data := uint32(),
+ offender := undefined | sockaddr()} |
+ #{error := term(),
+ origin := icmp,
+ type := time_exceeded | uint8(),
+ code := uint8(),
+ info := uint32(),
+ data := uint32(),
+ offender := undefined | sockaddr()} |
+ #{error := term(),
+ origin := icmp6,
+ type := dest_unreach,
+ code := icmpv6_dest_unreach(),
+ info := uint32(),
+ data := uint32(),
+ offender := undefined | sockaddr()} |
+ #{error := term(),
+ origin := icmp6,
+ type := pkt_toobig | time_exceeded | uint8(),
+ code := uint8(),
+ info := uint32(),
+ data := uint32(),
+ offender := undefined | sockaddr()} |
+ #{error := term(),
+ origin := ee_origin(),
+ type := uint8(),
+ code := uint8(),
+ info := uint32(),
+ data := uint32(),
+ offender := undefined | sockaddr()}.
+
+-type errcode() ::
+ inet:posix() | % closed | timeout | not_owner |
+ exalloc | exmonitor | exselect | exself.
+
+%% ===========================================================================
+%%
+%% Interface term formats
+%%
+
+-opaque select_tag() :: atom().
+-opaque select_ref() :: reference().
+
+-type select_info() :: {select_info, select_tag(), select_ref()}.
+
+-define(SELECT_INFO(T, R), {select_info, T, R}).
+-define(SELECT(T, R), {select, ?SELECT_INFO(T, R)}).
+
+
+%% ===========================================================================
+%%
+%% Defaults
+%%
+
+-define(ESOCK_LISTEN_BACKLOG_DEFAULT, 5).
+
+-define(ESOCK_ACCEPT_TIMEOUT_DEFAULT, infinity).
+
+-define(ESOCK_SEND_FLAGS_DEFAULT, []).
+-define(ESOCK_SEND_TIMEOUT_DEFAULT, infinity).
+-define(ESOCK_SENDTO_FLAGS_DEFAULT, []).
+-define(ESOCK_SENDTO_TIMEOUT_DEFAULT, ?ESOCK_SEND_TIMEOUT_DEFAULT).
+-define(ESOCK_SENDMSG_FLAGS_DEFAULT, []).
+-define(ESOCK_SENDMSG_TIMEOUT_DEFAULT, ?ESOCK_SEND_TIMEOUT_DEFAULT).
+
+-define(ESOCK_RECV_FLAGS_DEFAULT, []).
+-define(ESOCK_RECV_TIMEOUT_DEFAULT, infinity).
+
+
+%% ===========================================================================
+%%
+%% Administrative and utility API
+%%
+%% ===========================================================================
+
+%% *** number_of ***
+%%
+%% Interface function to the socket registry
+%% returns the number of existing (and "alive") sockets.
+%%
+-spec number_of() -> non_neg_integer().
+
+number_of() ->
+ ?REGISTRY:number_of().
+
+
+%% *** which_sockets/0,1 ***
+%%
+%% Interface function to the socket registry
+%% Returns a list of all the sockets, accoring to the filter rule.
+%%
+-spec which_sockets() -> [socket()].
+
+which_sockets() ->
+ ?REGISTRY:which_sockets(fun(_) -> true end).
+
+-spec which_sockets(FilterRule) -> [socket()] when
+ FilterRule :: inet | inet6 |
+ stream | dgram | seqpacket |
+ sctp | tcp | udp |
+ pid() |
+ fun((socket_info()) -> boolean()).
+
+which_sockets(Domain)
+ when ((Domain =:= inet) orelse (Domain =:= inet6)) ->
+ ?REGISTRY:which_sockets(fun(#{domain := D}) when (D =:= Domain) -> true;
+ (_) -> false end);
+which_sockets(Type)
+ when ((Type =:= stream) orelse (Type =:= dgram) orelse (Type =:= seqpacket)) ->
+ ?REGISTRY:which_sockets(fun(#{type := T}) when (T =:= Type) -> true;
+ (_) -> false end);
+which_sockets(Proto)
+ when ((Proto =:= sctp) orelse (Proto =:= tcp) orelse (Proto =:= udp)) ->
+ ?REGISTRY:which_sockets(fun(#{protocol := P}) when (P =:= Proto) -> true;
+ (_) -> false end);
+which_sockets(CTRL)
+ when is_pid(CTRL) ->
+ ?REGISTRY:which_sockets(fun(#{ctrl := C}) when (C =:= CTRL) -> true;
+ (_) -> false end);
+which_sockets(Filter) when is_function(Filter, 1) ->
+ ?REGISTRY:which_sockets(Filter).
+
+
+%% ===========================================================================
+%%
+%% Debug features
+%%
+%% ===========================================================================
+
+
+-spec info() -> map().
+%%
+info() ->
+ prim_socket:info().
+
+
+-spec debug(D :: boolean()) -> ok.
+%%
+debug(D) when is_boolean(D) ->
+ prim_socket:debug(D).
+
+
+-spec socket_debug(D :: boolean()) -> ok.
+%%
+socket_debug(D) when is_boolean(D) ->
+ prim_socket:socket_debug(D).
+
+
+%% ===========================================================================
+%%
+%% info - Get miscellaneous information about a socket.
+%%
+%% Generates a list of various info about the socket, such as counter values.
+%%
+%% Do *not* call this function often.
+%%
+%% ===========================================================================
+
+-spec info(Socket) -> socket_info() when
+ Socket :: socket().
+%%
+info(?mk_socket(SockRef)) when is_reference(SockRef) ->
+ prim_socket:info(SockRef);
+info(Socket) ->
+ erlang:error(badarg, [Socket]).
+
+
+%% ===========================================================================
+%%
+%% supports - get information about what the platform "supports".
+%%
+%% Generates a list of various info about what the plaform can support.
+%% The most obvious case is 'options'.
+%%
+%% Each item in a 'supports'-list will appear only *one* time.
+%%
+%% ===========================================================================
+
+-spec supports() -> [{Key1 :: term(),
+ boolean() | [{Key2 :: term(),
+ boolean() | [{Key3 :: term(),
+ boolean()}]}]}].
+supports() ->
+ [{Key1, supports(Key1)}
+ || Key1 <- [options, send_flags, recv_flags]]
+ ++ prim_socket:supports().
+
+-spec supports(Key1 :: term()) ->
+ [{Key2 :: term(),
+ boolean() | [{Key3 :: term(),
+ boolean()}]}].
+%%
+supports(options) ->
+ [{Level, supports(options, Level)}
+ || Level <- [socket, ip, ipv6, tcp, udp, sctp]];
+supports(Key) ->
+ prim_socket:supports(Key).
+
+-spec supports(Key1 :: term(), Key2 :: term()) ->
+ [{Key3 :: term(),
+ boolean()}].
+%%
+supports(Key1, Key2) ->
+ prim_socket:supports(Key1, Key2).
+
+
+-spec is_supported(Key1 :: term()) ->
+ boolean().
+is_supported(Key1) ->
+ get_is_supported(Key1, supports()).
+%%
+-spec is_supported(Key1 :: term(), Key2 :: term()) ->
+ boolean().
+is_supported(Key1, Key2) ->
+ get_is_supported(Key2, supports(Key1)).
+%%
+-spec is_supported(Key1 :: term(), Key2 :: term(), Key3 :: term()) ->
+ boolean().
+is_supported(Key1, Key2, Key3) ->
+ get_is_supported(Key3, supports(Key1, Key2)).
+
+
+get_is_supported(Key, Supported) ->
+ case lists:keyfind(Key, 1, Supported) of
+ false ->
+ false;
+ {_, Value} ->
+ if
+ is_boolean(Value) ->
+ Value;
+ is_list(Value) ->
+ false
+ end
+ end.
+
+
+%% ===========================================================================
+%%
+%% The proper socket API
+%%
+%% ===========================================================================
+
+%% ===========================================================================
+%%
+%% <KOLLA>
+%%
+%% The nif sets up a monitor to this process, and if it dies the socket
+%% is closed. It is also used if someone wants to monitor the socket.
+%%
+%% We may therefor need monitor function(s):
+%%
+%% socket:monitor(Socket)
+%% socket:demonitor(Socket)
+%%
+%% </KOLLA>
+%%
+
+%% ===========================================================================
+%%
+%% open - create an endpoint for communication
+%%
+
+-spec open(FD) -> {ok, Socket} | {error, Reason} when
+ FD :: integer(),
+ Socket :: socket(),
+ Reason :: errcode().
+
+open(FD) when is_integer(FD) ->
+ open(FD, #{});
+open(FD) ->
+ erlang:error(badarg, [FD]).
+
+-spec open(FD, Opts) -> {ok, Socket} | {error, Reason} when
+ FD :: integer(),
+ Opts ::
+ #{domain => domain(),
+ type => type(),
+ protocol => protocol(),
+ dup => boolean()},
+ Socket :: socket(),
+ Reason :: errcode();
+
+ (Domain, Type) -> {ok, Socket} | {error, Reason} when
+ Domain :: domain(),
+ Type :: type(),
+ Socket :: socket(),
+ Reason :: errcode().
+
+open(FD, Opts) when is_integer(FD), is_map(Opts) ->
+ case prim_socket:open(FD, Opts) of
+ {ok, SockRef} ->
+ Socket = ?mk_socket(SockRef),
+ {ok, Socket};
+ {error, _} = ERROR ->
+ ERROR
+ end;
+open(Domain, Type) ->
+ open(Domain, Type, default).
+
+-spec open(Domain, Type, Protocol) -> {ok, Socket} | {error, Reason} when
+ Domain :: domain(),
+ Type :: type(),
+ Protocol :: default | protocol(),
+ Socket :: socket(),
+ Reason :: errcode().
+
+open(Domain, Type, Protocol) ->
+ open(Domain, Type, Protocol, #{}).
+
+-spec open(Domain, Type, Protocol, Opts) -> {ok, Socket} | {error, Reason} when
+ Domain :: domain(),
+ Type :: type(),
+ Protocol :: default | protocol(),
+ Opts :: map(),
+ Socket :: socket(),
+ Reason :: errcode().
+
+open(Domain, Type, Protocol, Opts) when is_map(Opts) ->
+ case prim_socket:open(Domain, Type, Protocol, Opts) of
+ {ok, SockRef} ->
+ Socket = ?mk_socket(SockRef),
+ {ok, Socket};
+ {error, _} = ERROR ->
+ ERROR
+ end;
+open(Domain, Type, Protocol, Opts) ->
+ erlang:error(badarg, [Domain, Type, Protocol, Opts]).
+
+
+%% ===========================================================================
+%%
+%% bind - bind a name (an address) to a socket
+%%
+%% Note that the short (atom) addresses only work for some domains,
+%% and that the nif will reject 'broadcast' for other domains than 'inet'
+%%
+
+-spec bind(Socket, Addr) -> {ok, Port} | {error, Reason} when
+ Socket :: socket(),
+ Addr :: sockaddr() | any | broadcast | loopback,
+ Port :: port_number(),
+ Reason :: inet:posix() | closed.
+
+bind(?mk_socket(SockRef) = Socket, Addr) when is_reference(SockRef) ->
+ if
+ is_map(Addr) ->
+ prim_socket:bind(SockRef, Addr);
+ %%
+ Addr =:= any;
+ Addr =:= broadcast;
+ Addr =:= loopback ->
+ case prim_socket:getopt(SockRef, otp, domain) of
+ {ok, Domain}
+ when Domain =:= inet;
+ Domain =:= inet6 ->
+ prim_socket:bind(
+ SockRef, #{family => Domain, addr => Addr});
+ {ok, _Domain} ->
+ {error, eafnosupport};
+ {error, _} = ERROR ->
+ ERROR
+ end;
+ %%
+ true ->
+ erlang:error(badarg, [Socket, Addr])
+ end;
+bind(Socket, Addr) ->
+ erlang:error(badarg, [Socket, Addr]).
+
+
+%% ===========================================================================
+%%
+%% bind - Add or remove a bind addresses on a socket
+%%
+%% Calling this function is only valid if the socket is:
+%% type = seqpacket
+%% protocol = sctp
+%%
+%% If the domain is inet, then all addresses *must* be IPv4.
+%% If the domain is inet6, the addresses can be aither IPv4 or IPv6.
+%%
+
+-spec bind(Socket, Addrs, Action) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Addrs :: [sockaddr()],
+ Action :: add | remove,
+ Reason :: inet:posix() | closed.
+
+bind(?mk_socket(SockRef), Addrs, Action)
+ when is_reference(SockRef)
+ andalso is_list(Addrs)
+ andalso (Action =:= add
+ orelse Action =:= remove) ->
+ prim_socket:bind(SockRef, Addrs, Action);
+bind(Socket, Addrs, Action) ->
+ erlang:error(badarg, [Socket, Addrs, Action]).
+
+
+%% ===========================================================================
+%%
+%% connect - initiate a connection on a socket
+%%
+
+-spec connect(Socket) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Reason :: errcode() | closed.
+
+%% Finalize connect after connect(,, nowait) and received
+%% select message - see connect_deadline/3
+%%
+connect(?mk_socket(SockRef))
+ when is_reference(SockRef) ->
+ prim_socket:connect(SockRef);
+connect(Socket) ->
+ erlang:error(badarg, [Socket]).
+
+
+-spec connect(Socket, SockAddr) -> ok | {error, Reason} when
+ Socket :: socket(),
+ SockAddr :: sockaddr(),
+ Reason :: errcode() | closed.
+
+connect(Socket, SockAddr) ->
+ connect(Socket, SockAddr, infinity).
+
+
+-spec connect(Socket, SockAddr, nowait) ->
+ ok | {select, SelectInfo} | {error, Reason} when
+ Socket :: socket(),
+ SockAddr :: sockaddr(),
+ SelectInfo :: select_info(),
+ Reason :: errcode() | closed;
+
+ (Socket, SockAddr, Timeout) -> ok | {error, Reason} when
+ Socket :: socket(),
+ SockAddr :: sockaddr(),
+ Timeout :: timeout(),
+ Reason :: errcode() | closed | timeout.
+
+%% <KOLLA>
+%% Is it possible to connect with family = local for the (dest) sockaddr?
+%% </KOLLA>
+connect(?mk_socket(SockRef) = Socket, SockAddr, Timeout)
+ when is_reference(SockRef) ->
+ case deadline(Timeout) of
+ badarg = Reason ->
+ erlang:error(Reason, [Socket, SockAddr, Timeout]);
+ nowait ->
+ connect_nowait(SockRef, SockAddr);
+ Deadline ->
+ connect_deadline(SockRef, SockAddr, Deadline)
+ end;
+connect(Socket, SockAddr, Timeout) ->
+ erlang:error(badarg, [Socket, SockAddr, Timeout]).
+
+connect_nowait(SockRef, SockAddr) ->
+ case prim_socket:connect(SockRef, SockAddr) of
+ {select, Ref} ->
+ ?SELECT(connect, Ref);
+ Result ->
+ Result
+ end.
+
+connect_deadline(SockRef, SockAddr, Deadline) ->
+ case prim_socket:connect(SockRef, SockAddr) of
+ {select, Ref} ->
+ %% Connecting...
+ Timeout = timeout(Deadline),
+ receive
+ ?mk_socket_msg(_Socket, select, Ref) ->
+ prim_socket:connect(SockRef);
+ ?mk_socket_msg(_Socket, abort, {Ref, Reason}) ->
+ {error, Reason}
+ after Timeout ->
+ cancel(SockRef, connect, Ref),
+ {error, timeout}
+ end;
+ Result ->
+ Result
+ end.
+
+
+%% ===========================================================================
+%%
+%% listen - listen for connections on a socket
+%%
+
+-spec listen(Socket) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Reason :: inet:posix() | closed.
+
+listen(Socket) ->
+ listen(Socket, ?ESOCK_LISTEN_BACKLOG_DEFAULT).
+
+-spec listen(Socket, Backlog) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Backlog :: integer(),
+ Reason :: inet:posix() | closed.
+
+listen(?mk_socket(SockRef), Backlog)
+ when is_reference(SockRef), is_integer(Backlog) ->
+ prim_socket:listen(SockRef, Backlog);
+listen(Socket, Backlog) ->
+ erlang:error(badarg, [Socket, Backlog]).
+
+
+%% ===========================================================================
+%%
+%% accept, accept4 - accept a connection on a socket
+%%
+
+-spec accept(LSocket) -> {ok, Socket} | {error, Reason} when
+ LSocket :: socket(),
+ Socket :: socket(),
+ Reason :: errcode() | closed.
+
+accept(Socket) ->
+ accept(Socket, ?ESOCK_ACCEPT_TIMEOUT_DEFAULT).
+
+-spec accept(LSocket, nowait) ->
+ {ok, Socket} |
+ {select, SelectInfo} |
+ {error, Reason} when
+ LSocket :: socket(),
+ Socket :: socket(),
+ SelectInfo :: select_info(),
+ Reason :: errcode() | closed;
+
+ (LSocket, Timeout) -> {ok, Socket} | {error, Reason} when
+ LSocket :: socket(),
+ Timeout :: timeout(),
+ Socket :: socket(),
+ Reason :: errcode() | closed | timeout.
+
+accept(?mk_socket(LSockRef) = Socket, Timeout)
+ when is_reference(LSockRef) ->
+ case deadline(Timeout) of
+ badarg = Reason ->
+ erlang:error(Reason, [Socket, Timeout]);
+ nowait ->
+ accept_nowait(LSockRef);
+ Deadline ->
+ accept_deadline(LSockRef, Deadline)
+ end;
+accept(Socket, Timeout) ->
+ erlang:error(badarg, [Socket, Timeout]).
+
+accept_nowait(LSockRef) ->
+ AccRef = make_ref(),
+ case prim_socket:accept(LSockRef, AccRef) of
+ select ->
+ ?SELECT(accept, AccRef);
+ Result ->
+ accept_result(LSockRef, AccRef, Result)
+ end.
+
+accept_deadline(LSockRef, Deadline) ->
+ AccRef = make_ref(),
+ case prim_socket:accept(LSockRef, AccRef) of
+ select ->
+ %% Each call is non-blocking, but even then it takes
+ %% *some* time, so just to be sure, recalculate before
+ %% the receive.
+ Timeout = timeout(Deadline),
+ receive
+ ?mk_socket_msg(?mk_socket(LSockRef), select, AccRef) ->
+ accept_deadline(LSockRef, Deadline);
+ ?mk_socket_msg(_Socket, abort, {AccRef, Reason}) ->
+ {error, Reason}
+ after Timeout ->
+ cancel(LSockRef, accept, AccRef),
+ {error, timeout}
+ end;
+ Result ->
+ accept_result(LSockRef, AccRef, Result)
+ end.
+
+accept_result(LSockRef, AccRef, Result) ->
+ case Result of
+ {ok, SockRef} ->
+ Socket = ?mk_socket(SockRef),
+ {ok, Socket};
+ {error, _} = ERROR ->
+ cancel(LSockRef, accept, AccRef), % Just to be on the safe side...
+ ERROR
+ end.
+
+
+%% ===========================================================================
+%%
+%% send, sendto, sendmsg - send a message on a socket
+%%
+
+-spec send(Socket, Data) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Data :: iodata(),
+ Reason :: term().
+
+send(Socket, Data) ->
+ send(Socket, Data, ?ESOCK_SEND_FLAGS_DEFAULT, ?ESOCK_SEND_TIMEOUT_DEFAULT).
+
+-spec send(Socket, Data, Flags) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Data :: iodata(),
+ Flags :: send_flags(),
+ Reason :: {errcode() | closed,
+ Remaining :: pos_integer()};
+
+ (Socket, Data, Timeout :: nowait) ->
+ ok |
+ {ok, {binary(), SelectInfo}} |
+ {select, SelectInfo} |
+ {ok, {RestData, SelectInfo}} |
+ {error, Reason} when
+ Socket :: socket(),
+ Data :: iodata(),
+ RestData :: binary(),
+ SelectInfo :: select_info(),
+ Reason :: {errcode() | closed,
+ Remaining :: pos_integer()};
+
+ (Socket, Data, Timeout) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Data :: iodata(),
+ Timeout :: timeout(),
+ Reason :: {errcode() | closed | timeout,
+ Remaining :: pos_integer()}.
+
+send(Socket, Data, Flags) when is_list(Flags) ->
+ send(Socket, Data, Flags, ?ESOCK_SEND_TIMEOUT_DEFAULT);
+send(Socket, Data, Timeout) ->
+ send(Socket, Data, ?ESOCK_SEND_FLAGS_DEFAULT, Timeout).
+
+-spec send(Socket, Data, Flags, nowait) -> ok |
+ {select, SelectInfo} |
+ {ok, {RestData, SelectInfo}} |
+ {error, Reason} when
+ Socket :: socket(),
+ Data :: iodata(),
+ Flags :: send_flags(),
+ RestData :: binary(),
+ SelectInfo :: select_info(),
+ Reason :: {errcode() | closed,
+ Remaining :: pos_integer()};
+
+ (Socket, Data, Flags, Timeout) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Data :: iodata(),
+ Flags :: send_flags(),
+ Timeout :: timeout(),
+ Reason :: {errcode() | closed | timeout,
+ Remaining :: pos_integer()}.
+
+send(Socket, Data, Flags, Timeout) when is_list(Data) ->
+ Bin = erlang:list_to_binary(Data),
+ send(Socket, Bin, Flags, Timeout);
+send(?mk_socket(SockRef) = Socket, Data, Flags, Timeout)
+ when is_reference(SockRef), is_binary(Data), is_list(Flags) ->
+ To = undefined,
+ case deadline(Timeout) of
+ badarg = Reason ->
+ erlang:error(Reason, [Socket, Data, Flags, Timeout]);
+ nowait ->
+ send_common_nowait(SockRef, Data, To, Flags, send);
+ Deadline ->
+ send_common_deadline(SockRef, Data, To, Flags, Deadline, send)
+ end;
+send(Socket, Data, Flags, Timeout) ->
+ erlang:error(badarg, [Socket, Data, Flags, Timeout]).
+
+send_common_nowait(SockRef, Data, To, Flags, SendName) ->
+ SendRef = make_ref(),
+ case
+ case SendName of
+ send ->
+ prim_socket:send(SockRef, SendRef, Data, Flags);
+ sendto ->
+ prim_socket:sendto(SockRef, SendRef, Data, To, Flags)
+ end
+ of
+ {ok, Written} ->
+ %% We are partially done, but the user don't want to wait (here)
+ %% for completion
+ <<_:Written/binary, Rest/binary>> = Data,
+ {ok, {Rest, ?SELECT_INFO(SendName, SendRef)}};
+ select ->
+ ?SELECT(SendName, SendRef);
+ Result ->
+ send_common_result(Data, Result)
+ end.
+
+send_common_deadline(SockRef, Data, To, Flags, Deadline, SendName) ->
+ SendRef = make_ref(),
+ case
+ case SendName of
+ send ->
+ prim_socket:send(SockRef, SendRef, Data, Flags);
+ sendto ->
+ prim_socket:sendto(SockRef, SendRef, Data, To, Flags)
+ end
+ of
+ {ok, Written} ->
+ %% We are partially done, wait for continuation
+ Timeout = timeout(Deadline),
+ receive
+ ?mk_socket_msg(_Socket, select, SendRef)
+ when (Written > 0) ->
+ <<_:Written/binary, Rest/binary>> = Data,
+ send_common_deadline(
+ SockRef, Rest, To, Flags, Deadline, SendName);
+ ?mk_socket_msg(_Socket, select, SendRef) ->
+ send_common_deadline(
+ SockRef, Data, To, Flags, Deadline, SendName);
+ ?mk_socket_msg(_Socket, abort, {SendRef, Reason}) ->
+ {error, {Reason, byte_size(Data)}}
+ after Timeout ->
+ _ = cancel(SockRef, SendName, SendRef),
+ {error, {timeout, byte_size(Data)}}
+ end;
+ select ->
+ %% Wait for continuation
+ Timeout = timeout(Deadline),
+ receive
+ ?mk_socket_msg(_Socket, select, SendRef) ->
+ send_common_deadline(
+ SockRef, Data, To, Flags, Deadline, SendName);
+ ?mk_socket_msg(_Socket, abort, {SendRef, Reason}) ->
+ {error, {Reason, byte_size(Data)}}
+ after Timeout ->
+ _ = cancel(SockRef, SendName, SendRef),
+ {error, {timeout, byte_size(Data)}}
+ end;
+ %%
+ {error, ealready = Reason} ->
+ %% Internal error:
+ %% we called send, got eagain, and called send again
+ %% - without waiting for select message
+ erlang:error(Reason);
+ Result ->
+ send_common_result(Data, Result)
+ end.
+
+send_common_result(Data, Result) ->
+ case Result of
+ ok ->
+ ok;
+ {error, Reason} ->
+ {error, {Reason, byte_size(Data)}}
+ end.
+
+
+%% ---------------------------------------------------------------------------
+%%
+
+-spec sendto(Socket, Data, Dest) ->
+ ok | {error, Reason} when
+ Socket :: socket(),
+ Data :: binary(),
+ Dest :: sockaddr(),
+ Reason :: {errcode() | closed,
+ Remaining :: pos_integer()}.
+
+sendto(Socket, Data, Dest) ->
+ sendto(Socket, Data, Dest, ?ESOCK_SENDTO_FLAGS_DEFAULT).
+
+-spec sendto(Socket, Data, Dest, Flags) ->
+ ok | {error, Reason} when
+ Socket :: socket(),
+ Data :: binary(),
+ Dest :: sockaddr(),
+ Flags :: send_flags(),
+ Reason :: {errcode() | closed,
+ Remaining :: pos_integer()};
+
+ (Socket, Data, Dest, Timeout :: nowait) ->
+ ok |
+ {select, SelectInfo} |
+ {error, Reason} when
+ Socket :: socket(),
+ Data :: iodata(),
+ Dest :: sockaddr(),
+ SelectInfo :: select_info(),
+ Reason :: {errcode() | closed,
+ Remaining :: pos_integer()};
+
+ (Socket, Data, Dest, Timeout) ->
+ ok | {error, Reason} when
+ Socket :: socket(),
+ Data :: iodata(),
+ Dest :: sockaddr(),
+ Timeout :: timeout(),
+ Reason :: {errcode() | closed | timeout,
+ Remaining :: pos_integer()}.
+
+sendto(Socket, Data, Dest, Flags) when is_list(Flags) ->
+ sendto(Socket, Data, Dest, Flags, ?ESOCK_SENDTO_TIMEOUT_DEFAULT);
+sendto(Socket, Data, Dest, Timeout) ->
+ sendto(Socket, Data, Dest, ?ESOCK_SENDTO_FLAGS_DEFAULT, Timeout).
+
+
+-spec sendto(Socket, Data, Dest, Flags, nowait) ->
+ ok |
+ {ok, {binary(), SelectInfo}} |
+ {select, SelectInfo} |
+ {error, Reason} when
+ Socket :: socket(),
+ Data :: binary(),
+ Dest :: sockaddr(),
+ Flags :: send_flags(),
+ SelectInfo :: select_info(),
+ Reason :: {errcode() | closed,
+ Remaining :: pos_integer()};
+
+ (Socket, Data, Dest, Flags, Timeout) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Data :: binary(),
+ Dest :: sockaddr(),
+ Flags :: send_flags(),
+ Timeout :: timeout(),
+ Reason :: {errcode() | closed | timeout,
+ Remaining :: pos_integer()}.
+
+sendto(Socket, Data, Dest, Flags, Timeout) when is_list(Data) ->
+ Bin = erlang:list_to_binary(Data),
+ sendto(Socket, Bin, Dest, Flags, Timeout);
+sendto(?mk_socket(SockRef) = Socket, Data, Dest, Flags, Timeout)
+ when is_reference(SockRef), is_binary(Data), is_list(Flags) ->
+ case deadline(Timeout) of
+ badarg = Reason ->
+ erlang:error(Reason, [Socket, Data, Dest, Flags, Timeout]);
+ nowait ->
+ send_common_nowait(SockRef, Data, Dest, Flags, sendto);
+ Deadline ->
+ send_common_deadline(
+ SockRef, Data, Dest, Flags, Deadline, sendto)
+ end;
+sendto(Socket, Data, Dest, Flags, Timeout) ->
+ erlang:error(badarg, [Socket, Data, Dest, Flags, Timeout]).
+
+
+%% ---------------------------------------------------------------------------
+%%
+%% The only part of the msghdr() that *must* exist (a connected
+%% socket need not specify the addr field) is the iov.
+%% The ctrl field is optional, and the addr and flags are not
+%% used when sending.
+%%
+
+-spec sendmsg(Socket, MsgHdr) ->
+ ok |
+ {ok, Remaining} |
+ {error, Reason} when
+ Socket :: socket(),
+ MsgHdr :: msghdr(),
+ Remaining :: erlang:iovec(),
+ Reason :: term().
+
+sendmsg(Socket, MsgHdr) ->
+ sendmsg(Socket, MsgHdr,
+ ?ESOCK_SENDMSG_FLAGS_DEFAULT, ?ESOCK_SENDMSG_TIMEOUT_DEFAULT).
+
+
+-spec sendmsg(Socket, MsgHdr, Flags) -> ok | {error, Reason} when
+ Socket :: socket(),
+ MsgHdr :: msghdr(),
+ Flags :: send_flags(),
+ Reason :: errcode() | closed;
+
+ (Socket, MsgHdr, Timeout :: nowait) ->
+ ok |
+ {ok, Remaining} |
+ {error, Reason} when
+ Socket :: socket(),
+ MsgHdr :: msghdr(),
+ Remaining :: erlang:iovec(),
+ Reason :: errcode() | closed;
+
+ (Socket, MsgHdr, Timeout) -> ok | {error, Reason} when
+ Socket :: socket(),
+ MsgHdr :: msghdr(),
+ Timeout :: timeout(),
+ Reason :: errcode() | closed | timeout.
+
+sendmsg(Socket, MsgHdr, Flags) when is_list(Flags) ->
+ sendmsg(Socket, MsgHdr, Flags, ?ESOCK_SENDMSG_TIMEOUT_DEFAULT);
+sendmsg(Socket, MsgHdr, Timeout) ->
+ sendmsg(Socket, MsgHdr, ?ESOCK_SENDMSG_FLAGS_DEFAULT, Timeout).
+
+
+-spec sendmsg(Socket, MsgHdr, Flags, nowait) ->
+ ok |
+ {ok, Remaining} |
+ {error, Reason} when
+ Socket :: socket(),
+ MsgHdr :: msghdr(),
+ Flags :: send_flags(),
+ Remaining :: erlang:iovec(),
+ Reason :: errcode() | closed;
+
+ (Socket, MsgHdr, Flags, Timeout) ->
+ ok |
+ {ok, Remaining} |
+ {error, Reason} when
+ Socket :: socket(),
+ MsgHdr :: msghdr(),
+ Flags :: send_flags(),
+ Timeout :: timeout(),
+ Remaining :: erlang:iovec(),
+ Reason :: errcode() | closed | timeout.
+
+sendmsg(?mk_socket(SockRef) = Socket, MsgHdr, Flags, Timeout)
+ when is_reference(SockRef), is_map(MsgHdr), is_list(Flags) ->
+ case deadline(Timeout) of
+ badarg = Reason ->
+ erlang:error(Reason, [Socket, MsgHdr, Flags, Timeout]);
+ Deadline ->
+ sendmsg_loop(SockRef, MsgHdr, Flags, Deadline)
+ end;
+sendmsg(Socket, MsgHdr, Flags, Timeout) ->
+ erlang:error(badarg, [Socket, MsgHdr, Flags, Timeout]).
+
+sendmsg_loop(SockRef, MsgHdr, Flags, Deadline) ->
+ SendRef = make_ref(),
+ case prim_socket:sendmsg(SockRef, SendRef, MsgHdr, Flags) of
+ ok ->
+ %% We are done
+ ok;
+ %%
+ {ok, Written} when is_integer(Written) andalso (Written > 0) ->
+ %% We should not retry here since the protocol may not
+ %% be able to handle a message being split. Leave it to
+ %% the caller to figure out (call again with the rest).
+ %%
+ %% We need to cancel this partial write.
+ %%
+ _ = cancel(SockRef, sendmsg, SendRef),
+ {ok, sendmsg_rest(maps:get(iov, MsgHdr), Written)};
+ %%
+ select when (Deadline =:= nowait) ->
+ ?SELECT(sendmsg, SendRef);
+ select ->
+ Timeout = timeout(Deadline),
+ receive
+ ?mk_socket_msg(?mk_socket(SockRef), select, SendRef) ->
+ sendmsg_loop(SockRef, MsgHdr, Flags, Deadline);
+ ?mk_socket_msg(_Socket, abort, {SendRef, Reason}) ->
+ {error, Reason}
+ after Timeout ->
+ _ = cancel(SockRef, sendmsg, SendRef),
+ {error, timeout}
+ end;
+ %%
+ {error, ealready = Reason} when Deadline =/= nowait ->
+ %% Internal error:
+ %% we called send, got eagain, and called send again
+ %% - without waiting for select message
+ erlang:error(Reason);
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+sendmsg_rest([B|IOVec], Written) when Written >= byte_size(B) ->
+ sendmsg_rest(IOVec, Written - byte_size(B));
+sendmsg_rest([B|IOVec], Written) ->
+ <<_:Written/binary, Rest/binary>> = B,
+ [Rest|IOVec].
+
+
+%% ===========================================================================
+%%
+%% recv, recvfrom, recvmsg - receive a message from a socket
+%%
+%% Description:
+%% There is a special case for the argument Length. If its set to zero (0),
+%% it means "give me everything you have".
+%%
+%% Returns: {ok, Binary} | {error, Reason}
+%% Binary - The received data as a binary
+%% Reason - The error reason:
+%% timeout | {timeout, AccData} |
+%% posix() | {posix(), AccData} |
+%% atom() | {atom(), AccData}
+%% AccData - The data (as a binary) that we did manage to receive
+%% before the timeout.
+%%
+%% Arguments:
+%% Socket - The socket to read from.
+%% Length - The number of bytes to read.
+%% Flags - A list of "options" for the read.
+%% Timeout - Time-out in milliseconds.
+
+-spec recv(Socket) ->
+ {ok, Data} |
+ {error, Reason} when
+ Socket :: socket(),
+ Data :: binary(),
+ Reason ::
+ errcode() | closed |
+ {errcode() | closed, Data :: binary()}.
+
+recv(Socket) ->
+ recv(Socket, 0).
+
+-spec recv(Socket, Length) ->
+ {ok, Data} |
+ {error, Reason} when
+ Socket :: socket(),
+ Length :: non_neg_integer(),
+ Data :: binary(),
+ Reason ::
+ errcode() | closed |
+ {errcode() | closed, Data :: binary()}.
+
+recv(Socket, Length) ->
+ recv(Socket, Length,
+ ?ESOCK_RECV_FLAGS_DEFAULT,
+ ?ESOCK_RECV_TIMEOUT_DEFAULT).
+
+-spec recv(Socket, Length, Flags) ->
+ {ok, Data} |
+ {error, Reason} when
+ Socket :: socket(),
+ Length :: non_neg_integer(),
+ Flags :: recv_flags(),
+ Data :: binary(),
+ Reason ::
+ errcode() | closed |
+ {errcode() | closed, Data :: binary()};
+
+ (Socket, Length, Timeout :: nowait) ->
+ {ok, Data} |
+ {ok, {Data, SelectInfo}} |
+ {select, SelectInfo} |
+ {error, Reason} when
+ Socket :: socket(),
+ Length :: non_neg_integer(),
+ Data :: binary(),
+ SelectInfo :: select_info(),
+ Reason ::
+ errcode() | closed |
+ {errcode() | closed, Data :: binary()};
+
+ (Socket, Length, Timeout) ->
+ {ok, Data} |
+ {error, Reason} when
+ Socket :: socket(),
+ Length :: non_neg_integer(),
+ Timeout :: timeout(),
+ Data :: binary(),
+ Reason ::
+ errcode() | closed | timeout |
+ {errcode() | closed | timeout, Data :: binary()}.
+
+recv(Socket, Length, Flags) when is_list(Flags) ->
+ recv(Socket, Length, Flags, ?ESOCK_RECV_TIMEOUT_DEFAULT);
+recv(Socket, Length, Timeout) ->
+ recv(Socket, Length, ?ESOCK_RECV_FLAGS_DEFAULT, Timeout).
+
+-spec recv(Socket, Length, Flags, nowait) ->
+ {ok, Data} |
+ {ok, {Data, SelectInfo}} |
+ {select, SelectInfo} |
+ {error, Reason} when
+ Socket :: socket(),
+ Length :: non_neg_integer(),
+ Flags :: recv_flags(),
+ Data :: binary(),
+ SelectInfo :: select_info(),
+ Reason ::
+ errcode() | closed |
+ {errcode() | closed, Data :: binary()};
+
+ (Socket, Length, Flags, Timeout) ->
+ {ok, Data} |
+ {error, Reason} when
+ Socket :: socket(),
+ Length :: non_neg_integer(),
+ Flags :: recv_flags(),
+ Timeout :: timeout(),
+ Data :: binary(),
+ Reason ::
+ errcode() | closed | timeout |
+ {errcode() | closed | timeout, Data :: binary()}.
+
+recv(?mk_socket(SockRef) = Socket, Length, Flags, Timeout)
+ when is_reference(SockRef),
+ is_integer(Length), Length >= 0,
+ is_list(Flags) ->
+ case deadline(Timeout) of
+ badarg = Reason ->
+ erlang:error(Reason, [Socket, Length, Flags, Timeout]);
+ nowait ->
+ recv_nowait(SockRef, Length, Flags, <<>>);
+ Deadline ->
+ recv_deadline(SockRef, Length, Flags, Deadline, <<>>)
+ end;
+recv(Socket, Length, Flags, Timeout) ->
+ erlang:error(badarg, [Socket, Length, Flags, Timeout]).
+
+%% We will only recurse with Length == 0 if Length is 0,
+%% so Length == 0 means to return all available data also when recursing
+
+recv_nowait(SockRef, Length, Flags, Acc) ->
+ RecvRef = make_ref(),
+ case prim_socket:recv(SockRef, RecvRef, Length, Flags) of
+ {more, Bin} ->
+ %% We got what we requested but will not waste more time
+ %% although there might be more data available
+ {ok, bincat(Acc, Bin)};
+ {select, Bin} ->
+ %% We got less than requested so the caller will
+ %% get a select message when there might be more to read
+ {ok, {bincat(Acc, Bin), ?SELECT_INFO(recv, RecvRef)}};
+ select ->
+ %% The caller will get a select message when there
+ %% might me data to read
+ if
+ byte_size(Acc) =:= 0 ->
+ ?SELECT(recv, RecvRef);
+ true ->
+ {ok, {Acc, ?SELECT_INFO(recv, RecvRef)}}
+ end;
+ Result ->
+ recv_result(Acc, Result)
+ end.
+
+recv_deadline(SockRef, Length, Flags, Deadline, Acc) ->
+ RecvRef = make_ref(),
+ case prim_socket:recv(SockRef, RecvRef, Length, Flags) of
+ {more, Bin} ->
+ %% There is more data readily available
+ %% - repeat unless time's up
+ Timeout = timeout(Deadline),
+ if
+ 0 < Timeout ->
+ %% Recv more
+ recv_deadline(
+ SockRef, Length, Flags, Deadline, bincat(Acc, Bin));
+ true ->
+ {ok, bincat(Acc, Bin)}
+ end;
+ %%
+ {select, Bin} ->
+ %% We got less than requested
+ Timeout = timeout(Deadline),
+ receive
+ ?mk_socket_msg(?mk_socket(SockRef), select, RecvRef) ->
+ if
+ 0 < Timeout ->
+ %% Recv more
+ recv_deadline(
+ SockRef, Length - byte_size(Bin), Flags,
+ Deadline, bincat(Acc, Bin));
+ true ->
+ {error, {timeout, bincat(Acc, Bin)}}
+ end;
+ ?mk_socket_msg(_Socket, abort, {RecvRef, Reason}) ->
+ {error, {Reason, bincat(Acc, Bin)}}
+ after Timeout ->
+ cancel(SockRef, recv, RecvRef),
+ {error, {timeout, bincat(Acc, Bin)}}
+ end;
+ %%
+ select when Length =:= 0, 0 < byte_size(Acc) ->
+ %% We first got some data and are then asked to wait,
+ %% but we only want the first that comes
+ %% - cancel and return what we have
+ cancel(SockRef, recv, RecvRef),
+ {ok, Acc};
+ select ->
+ %% There is nothing just now, but we will be notified when there
+ %% is something to read (a select message).
+ Timeout = timeout(Deadline),
+ receive
+ ?mk_socket_msg(?mk_socket(SockRef), select, RecvRef) ->
+ if
+ 0 < Timeout ->
+ %% Retry
+ recv_deadline(
+ SockRef, Length, Flags, Deadline, Acc);
+ true ->
+ recv_error(Acc, timeout)
+ end;
+ ?mk_socket_msg(_Socket, abort, {RecvRef, Reason}) ->
+ recv_error(Acc, Reason)
+ after Timeout ->
+ cancel(SockRef, recv, RecvRef),
+ recv_error(Acc, timeout)
+ end;
+ %%
+ {error, ealready = Reason} ->
+ %% Internal error:
+ %% we called recv, got eagain, and called recv again
+ %% - without waiting for select message
+ erlang:error(Reason);
+ Result ->
+ recv_result(Acc, Result)
+ end.
+
+recv_result(Acc, Result) ->
+ case Result of
+ {ok, Bin} ->
+ {ok, bincat(Acc, Bin)};
+ {error, _} = ERROR when byte_size(Acc) =:= 0 ->
+ ERROR;
+ {error, Reason} ->
+ {error, {Reason, Acc}}
+ end.
+
+recv_error(Acc, Reason) ->
+ if
+ byte_size(Acc) =:= 0 ->
+ {error, Reason};
+ true ->
+ {error, {Reason, Acc}}
+ end.
+
+%% ---------------------------------------------------------------------------
+%%
+%% With recvfrom we get messages, which means that regardless of how
+%% much we want to read, we return when we get a message.
+%% The MaxSize argument basically defines the size of our receive
+%% buffer. By setting the size to zero (0), we use the configured
+%% size (see setopt).
+%% It may be impossible to know what (buffer) size is appropriate
+%% "in advance", and in those cases it may be convenient to use the
+%% (recv) 'peek' flag. When this flag is provided the message is *not*
+%% "consumed" from the underlying (OS) buffers, so another recvfrom call
+%% is needed, possibly with a then adjusted buffer size.
+%%
+
+-spec recvfrom(Socket) -> {ok, {Source, Data}} | {error, Reason} when
+ Socket :: socket(),
+ Source :: sockaddr() | undefined,
+ Data :: binary(),
+ Reason :: errcode() | closed.
+
+recvfrom(Socket) ->
+ recvfrom(Socket, 0).
+
+-spec recvfrom(Socket, BufSz) -> {ok, {Source, Data}} | {error, Reason} when
+ Socket :: socket(),
+ BufSz :: non_neg_integer(),
+ Source :: sockaddr() | undefined,
+ Data :: binary(),
+ Reason :: errcode() | closed.
+
+recvfrom(Socket, BufSz) ->
+ recvfrom(Socket, BufSz,
+ ?ESOCK_RECV_FLAGS_DEFAULT,
+ ?ESOCK_RECV_TIMEOUT_DEFAULT).
+
+-spec recvfrom(Socket, Flags, nowait) ->
+ {ok, {Source, Data}} |
+ {select, SelectInfo} |
+ {error, Reason} when
+ Socket :: socket(),
+ Flags :: recv_flags(),
+ Source :: sockaddr() | undefined,
+ Data :: binary(),
+ SelectInfo :: select_info(),
+ Reason :: errcode() | closed;
+
+ (Socket, Flags, Timeout) ->
+ {ok, {Source, Data}} |
+ {error, Reason} when
+ Socket :: socket(),
+ Flags :: recv_flags(),
+ Timeout :: timeout(),
+ Source :: sockaddr() | undefined,
+ Data :: binary(),
+ Reason :: errcode() | closed | timeout;
+
+ (Socket, BufSz, Flags) ->
+ {ok, {Source, Data}} | {error, Reason} when
+ Socket :: socket(),
+ BufSz :: non_neg_integer(),
+ Flags :: recv_flags(),
+ Source :: sockaddr() | undefined,
+ Data :: binary(),
+ Reason :: errcode() | closed;
+
+ (Socket, BufSz, nowait) ->
+ {ok, {Source, Data}} |
+ {select, SelectInfo} |
+ {error, Reason} when
+ Socket :: socket(),
+ BufSz :: non_neg_integer(),
+ Source :: sockaddr() | undefined,
+ Data :: binary(),
+ SelectInfo :: select_info(),
+ Reason :: errcode() | closed;
+
+ (Socket, BufSz, Timeout) ->
+ {ok, {Source, Data}} | {error, Reason} when
+ Socket :: socket(),
+ BufSz :: non_neg_integer(),
+ Timeout :: timeout(),
+ Source :: sockaddr() | undefined,
+ Data :: binary(),
+ Reason :: errcode() | closed | timeout.
+
+recvfrom(Socket, Flags, Timeout) when is_list(Flags) ->
+ recvfrom(Socket, 0, Flags, Timeout);
+recvfrom(Socket, BufSz, Flags) when is_list(Flags) ->
+ recvfrom(Socket, BufSz, Flags, ?ESOCK_RECV_TIMEOUT_DEFAULT);
+recvfrom(Socket, BufSz, Timeout) ->
+ recvfrom(Socket, BufSz, ?ESOCK_RECV_FLAGS_DEFAULT, Timeout).
+
+-spec recvfrom(Socket, BufSz, Flags, nowait) ->
+ {ok, {Source, Data}} |
+ {select, SelectInfo} |
+ {error, Reason} when
+ Socket :: socket(),
+ BufSz :: non_neg_integer(),
+ Flags :: recv_flags(),
+ Source :: sockaddr() | undefined,
+ Data :: binary(),
+ SelectInfo :: select_info(),
+ Reason :: errcode() | closed;
+
+ (Socket, BufSz, Flags, Timeout) ->
+ {ok, {Source, Data}} |
+ {error, Reason} when
+ Socket :: socket(),
+ BufSz :: non_neg_integer(),
+ Flags :: recv_flags(),
+ Timeout :: timeout(),
+ Source :: sockaddr() | undefined,
+ Data :: binary(),
+ Reason :: errcode() | closed | timeout.
+
+recvfrom(?mk_socket(SockRef) = Socket, BufSz, Flags, Timeout)
+ when is_reference(SockRef),
+ is_integer(BufSz), 0 =< BufSz,
+ is_list(Flags) ->
+ case deadline(Timeout) of
+ badarg = Reason ->
+ erlang:error(Reason, [Socket, BufSz, Flags, Timeout]);
+ nowait ->
+ recvfrom_nowait(SockRef, BufSz, Flags);
+ Deadline ->
+ recvfrom_deadline(SockRef, BufSz, Flags, Deadline)
+ end;
+recvfrom(Socket, BufSz, Flags, Timeout) ->
+ erlang:error(badarg, [Socket, BufSz, Flags, Timeout]).
+
+recvfrom_nowait(SockRef, BufSz, Flags) ->
+ RecvRef = make_ref(),
+ case prim_socket:recvfrom(SockRef, RecvRef, BufSz, Flags) of
+ select ->
+ ?SELECT(recvfrom, RecvRef);
+ Result ->
+ recvfrom_result(Result)
+ end.
+
+recvfrom_deadline(SockRef, BufSz, Flags, Deadline) ->
+ RecvRef = make_ref(),
+ case prim_socket:recvfrom(SockRef, RecvRef, BufSz, Flags) of
+ select ->
+ %% There is nothing just now, but we will be notified when there
+ %% is something to read (a select message).
+ Timeout = timeout(Deadline),
+ receive
+ ?mk_socket_msg(?mk_socket(SockRef), select, RecvRef) ->
+ recvfrom_deadline(SockRef, BufSz, Flags, Deadline);
+ ?mk_socket_msg(_Socket, abort, {RecvRef, Reason}) ->
+ {error, Reason}
+ after Timeout ->
+ cancel(SockRef, recvfrom, RecvRef),
+ {error, timeout}
+ end;
+ {error, ealready = Reason} ->
+ %% Internal error:
+ %% we called recvfrom, got eagain, and called recvfrom again
+ %% - without waiting for select message
+ erlang:error(Reason);
+ Result ->
+ recvfrom_result(Result)
+ end.
+
+recvfrom_result(Result) ->
+ case Result of
+ {ok, {_Source, _NewData}} = OK ->
+ OK;
+ {error, _Reason} = ERROR ->
+ ERROR
+ end.
+
+
+%% ---------------------------------------------------------------------------
+%%
+
+-spec recvmsg(Socket) -> {ok, MsgHdr} | {error, Reason} when
+ Socket :: socket(),
+ MsgHdr :: msghdr(),
+ Reason :: errcode() | closed.
+
+recvmsg(Socket) ->
+ recvmsg(Socket, 0, 0,
+ ?ESOCK_RECV_FLAGS_DEFAULT, ?ESOCK_RECV_TIMEOUT_DEFAULT).
+
+-spec recvmsg(Socket, Flags) -> {ok, MsgHdr} | {error, Reason} when
+ Socket :: socket(),
+ Flags :: recv_flags(),
+ MsgHdr :: msghdr(),
+ Reason :: errcode() | closed;
+
+ (Socket, Timeout :: nowait) -> {ok, MsgHdr} |
+ {select, SelectInfo} |
+ {error, Reason} when
+ Socket :: socket(),
+ MsgHdr :: msghdr(),
+ SelectInfo :: select_info(),
+ Reason :: errcode() | closed;
+
+ (Socket, Timeout) -> {ok, MsgHdr} | {error, Reason} when
+ Socket :: socket(),
+ Timeout :: timeout(),
+ MsgHdr :: msghdr(),
+ Reason :: errcode() | closed | timeout.
+
+recvmsg(Socket, Flags) when is_list(Flags) ->
+ recvmsg(Socket, 0, 0, Flags, ?ESOCK_RECV_TIMEOUT_DEFAULT);
+recvmsg(Socket, Timeout) ->
+ recvmsg(Socket, 0, 0, ?ESOCK_RECV_FLAGS_DEFAULT, Timeout).
+
+-spec recvmsg(Socket, Flags, nowait) -> {ok, MsgHdr} |
+ {select, SelectInfo} |
+ {error, Reason} when
+ Socket :: socket(),
+ Flags :: recv_flags(),
+ MsgHdr :: msghdr(),
+ SelectInfo :: select_info(),
+ Reason :: errcode() | closed;
+
+ (Socket, Flags, Timeout) -> {ok, MsgHdr} | {error, Reason} when
+ Socket :: socket(),
+ Flags :: recv_flags(),
+ Timeout :: timeout(),
+ MsgHdr :: msghdr(),
+ Reason :: errcode() | closed | timeout;
+
+ (Socket, BufSz, CtrlSz) -> {ok, MsgHdr} | {error, Reason} when
+ Socket :: socket(),
+ BufSz :: non_neg_integer(),
+ CtrlSz :: non_neg_integer(),
+ MsgHdr :: msghdr(),
+ Reason :: errcode() | closed.
+
+recvmsg(Socket, Flags, Timeout) when is_list(Flags) ->
+ recvmsg(Socket, 0, 0, Flags, Timeout);
+recvmsg(Socket, BufSz, CtrlSz) when is_integer(BufSz), is_integer(CtrlSz) ->
+ recvmsg(Socket, BufSz, CtrlSz,
+ ?ESOCK_RECV_FLAGS_DEFAULT, ?ESOCK_RECV_TIMEOUT_DEFAULT).
+
+
+-spec recvmsg(Socket, BufSz, CtrlSz, Flags, nowait) ->
+ {ok, MsgHdr} |
+ {select, SelectInfo} |
+ {error, Reason} when
+ Socket :: socket(),
+ BufSz :: non_neg_integer(),
+ CtrlSz :: non_neg_integer(),
+ Flags :: recv_flags(),
+ MsgHdr :: msghdr(),
+ SelectInfo :: select_info(),
+ Reason :: errcode() | closed;
+
+ (Socket, BufSz, CtrlSz, Flags, Timeout) ->
+ {ok, MsgHdr} |
+ {error, Reason} when
+ Socket :: socket(),
+ BufSz :: non_neg_integer(),
+ CtrlSz :: non_neg_integer(),
+ Flags :: recv_flags(),
+ Timeout :: timeout(),
+ MsgHdr :: msghdr(),
+ Reason :: errcode() | closed | timeout.
+
+recvmsg(?mk_socket(SockRef) = Socket, BufSz, CtrlSz, Flags, Timeout)
+ when is_reference(SockRef),
+ is_integer(BufSz), 0 =< BufSz,
+ is_integer(CtrlSz), 0 =< CtrlSz,
+ is_list(Flags) ->
+ case deadline(Timeout) of
+ badarg = Reason ->
+ erlang:error(Reason, [Socket, BufSz, CtrlSz, Flags, Timeout]);
+ nowait ->
+ recvmsg_nowait(SockRef, BufSz, CtrlSz, Flags);
+ Deadline ->
+ recvmsg_deadline(SockRef, BufSz, CtrlSz, Flags, Deadline)
+ end;
+recvmsg(Socket, BufSz, CtrlSz, Flags, Timeout) ->
+ erlang:error(badarg, [Socket, BufSz, CtrlSz, Flags, Timeout]).
+
+recvmsg_nowait(SockRef, BufSz, CtrlSz, Flags) ->
+ RecvRef = make_ref(),
+ case prim_socket:recvmsg(SockRef, RecvRef, BufSz, CtrlSz, Flags) of
+ select ->
+ ?SELECT(recvmsg, RecvRef);
+ Result ->
+ recvmsg_result(Result)
+ end.
+
+recvmsg_deadline(SockRef, BufSz, CtrlSz, Flags, Deadline) ->
+ RecvRef = make_ref(),
+ case prim_socket:recvmsg(SockRef, RecvRef, BufSz, CtrlSz, Flags) of
+ select ->
+ %% There is nothing just now, but we will be notified when there
+ %% is something to read (a select message).
+ Timeout = timeout(Deadline),
+ receive
+ ?mk_socket_msg(?mk_socket(SockRef), select, RecvRef) ->
+ recvmsg_deadline(
+ SockRef, BufSz, CtrlSz, Flags, Deadline);
+ ?mk_socket_msg(_Socket, abort, {RecvRef, Reason}) ->
+ {error, Reason}
+ after Timeout ->
+ cancel(SockRef, recvmsg, RecvRef),
+ {error, timeout}
+ end;
+ %%
+ {error, ealready = Reason} ->
+ %% Internal error:
+ %% we called recvmsg, got eagain, and called recvmsg again
+ %% - without waiting for select message
+ erlang:error(Reason);
+ Result ->
+ recvmsg_result(Result)
+ end.
+
+recvmsg_result(Result) ->
+ case Result of
+ {ok, _MsgHdr} = OK ->
+ OK;
+ {error, _Reason} = ERROR ->
+ ERROR
+ end.
+
+
+%% ===========================================================================
+%%
+%% close - close a file descriptor
+%%
+%% Closing a socket is a two stage rocket (because of linger).
+%% We need to perform the actual socket close while in BLOCKING mode.
+%% But that would hang the entire VM, so what we do is divide the
+%% close in two steps:
+%% 1) prim_socket:nif_close + the socket_stop (nif) callback function
+%% This is for everything that can be done safely NON-BLOCKING.
+%% 2) prim_socket:nif_finalize_close which is executed by a *dirty* scheduler
+%% Before we call the socket close function, we set the socket
+%% BLOCKING. Thereby linger is handled properly.
+
+-spec close(Socket) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Reason :: errcode() | closed | timeout.
+
+close(?mk_socket(SockRef))
+ when is_reference(SockRef) ->
+ case prim_socket:close(SockRef) of
+ ok ->
+ prim_socket:finalize_close(SockRef);
+ {ok, CloseRef} ->
+ %% We must wait for the socket_stop callback function to
+ %% complete its work
+ receive
+ ?mk_socket_msg(?mk_socket(SockRef), close, CloseRef) ->
+ prim_socket:finalize_close(SockRef)
+ end;
+ {error, _} = ERROR ->
+ ERROR
+ end;
+close(Socket) ->
+ erlang:error(badarg, [Socket]).
+
+
+
+%% ===========================================================================
+%%
+%% shutdown - shut down part of a full-duplex connection
+%%
+
+-spec shutdown(Socket, How) -> ok | {error, Reason} when
+ Socket :: socket(),
+ How :: shutdown_how(),
+ Reason :: inet:posix() | closed.
+
+shutdown(?mk_socket(SockRef), How)
+ when is_reference(SockRef) ->
+ prim_socket:shutdown(SockRef, How);
+shutdown(Socket, How) ->
+ erlang:error(badarg, [Socket, How]).
+
+
+%% ===========================================================================
+%%
+%% setopt - manipulate individual properties of a socket
+%%
+%% What properties are valid depend on what kind of socket it is
+%% (domain, type and protocol)
+%% If its an "invalid" option (or value), we should not crash but return some
+%% useful error...
+%%
+%% <KOLLA>
+%%
+%% WE NEED TO MAKE SURE THAT THE USER DOES NOT MAKE US BLOCKING
+%% AS MUCH OF THE CODE EXPECTS TO BE NON-BLOCKING!!
+%%
+%% </KOLLA>
+
+-spec setopt(Socket, otp, otp_socket_option(), Value) ->
+ ok | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: errcode() | closed | not_owner;
+
+ (Socket, socket, socket_option(), Value) ->
+ ok | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: inet:posix() | closed;
+
+ (Socket, ip, ip_socket_option(), Value) ->
+ ok | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: inet:posix() | closed;
+
+ (Socket, ipv6, ipv6_socket_option(), Value) ->
+ ok | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: inet:posix() | closed;
+
+ (Socket, tcp, tcp_socket_option(), Value) ->
+ ok | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: inet:posix() | closed;
+
+ (Socket, udp, udp_socket_option(), Value) ->
+ ok | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: inet:posix() | closed;
+
+ (Socket, sctp, sctp_socket_option(), Value) ->
+ ok | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: inet:posix() | closed;
+
+ (Socket, Level, Key, Value) ->
+ ok | {error, Reason} when
+ Socket :: socket(),
+ Level :: non_neg_integer(),
+ Key :: non_neg_integer(),
+ Value :: binary(),
+ Reason :: inet:posix() | closed.
+
+setopt(?mk_socket(SockRef), Level, Key, Value)
+ when is_reference(SockRef) ->
+ prim_socket:setopt(SockRef, Level, Key, Value);
+setopt(Socket, Level, Key, Value) ->
+ erlang:error(badarg, [Socket, Level, Key, Value]).
+
+
+%% ===========================================================================
+%%
+%% getopt - retrieve individual properties of a socket
+%%
+%% What properties are valid depend on what kind of socket it is
+%% (domain, type and protocol).
+%% If its an "invalid" option, we should not crash but return some
+%% useful error...
+%%
+%% When specifying level as an integer, and therefor using "native mode",
+%% we should make it possible to specify common types instead of the
+%% value size. Example: int | bool | {string, pos_integer()} | non_neg_integer()
+%%
+
+-spec getopt(Socket, otp, otp_socket_option()) ->
+ {ok, Value} | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: einval | closed;
+
+ (Socket, socket, socket_option()) ->
+ {ok, Value} | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: inet:posix() | closed;
+
+ (Socket, ip, ip_socket_option()) ->
+ {ok, Value} | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: inet:posix() | closed;
+
+ (Socket, ipv6, ipv6_socket_option()) ->
+ {ok, Value} | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: inet:posix() | closed;
+
+ (Socket, tcp, tcp_socket_option()) ->
+ {ok, Value} | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: inet:posix() | closed;
+
+ (Socket, udp, udp_socket_option()) ->
+ {ok, Value} | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: inet:posix() | closed;
+
+ (Socket, sctp, sctp_socket_option()) ->
+ {ok, Value} | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: inet:posix() | closed;
+
+ (Socket, Level, Key) ->
+ ok | {ok, Value} | {error, Reason} when
+ Socket :: socket(),
+ Level :: integer(),
+ Key :: {NativeOpt, ValueSize},
+ NativeOpt :: integer(),
+ ValueSize :: int | bool | non_neg_integer(),
+ Value :: term(),
+ Reason :: inet:posix() | closed.
+
+getopt(?mk_socket(SockRef), Level, Key)
+ when is_reference(SockRef) ->
+ prim_socket:getopt(SockRef, Level, Key);
+getopt(Socket, Level, Key) ->
+ erlang:error(badarg, [Socket, Level, Key]).
+
+
+%% ===========================================================================
+%%
+%% sockname - return the current address of the socket.
+%%
+%%
+
+-spec sockname(Socket) -> {ok, SockAddr} | {error, Reason} when
+ Socket :: socket(),
+ SockAddr :: sockaddr(),
+ Reason :: inet:posix() | closed.
+
+sockname(?mk_socket(SockRef))
+ when is_reference(SockRef) ->
+ prim_socket:sockname(SockRef);
+sockname(Socket) ->
+ erlang:error(badarg, [Socket]).
+
+
+%% ===========================================================================
+%%
+%% peername - return the address of the peer *connected* to the socket.
+%%
+%%
+
+-spec peername(Socket) -> {ok, SockAddr} | {error, Reason} when
+ Socket :: socket(),
+ SockAddr :: sockaddr(),
+ Reason :: inet:posix() | closed.
+
+peername(?mk_socket(SockRef))
+ when is_reference(SockRef) ->
+ prim_socket:peername(SockRef);
+peername(Socket) ->
+ erlang:error(badarg, [Socket]).
+
+
+%% ===========================================================================
+%%
+%% cancel - cancel an operation resulting in a select
+%%
+%% A call to accept, recv/recvfrom/recvmsg and send/sendto/sendmsg
+%% can result in a select if they are called with the Timeout argument
+%% set to nowait. This is indicated by the return of the select-info.
+%% Such a operation can be cancelled by calling this function.
+%%
+
+-spec cancel(Socket, SelectInfo) -> ok | {error, Reason} when
+ Socket :: socket(),
+ SelectInfo :: select_info(),
+ Reason :: einval | closed | exself.
+
+cancel(?mk_socket(SockRef), ?SELECT_INFO(Tag, Ref))
+ when is_reference(SockRef) ->
+ cancel(SockRef, Tag, Ref);
+cancel(Socket, SelectInfo) ->
+ erlang:error(badarg, [Socket, SelectInfo]).
+
+
+cancel(SockRef, Op, OpRef) ->
+ case prim_socket:cancel(SockRef, Op, OpRef) of
+ %% The select has already completed
+ {error, select_sent} ->
+ flush_select_msg(SockRef, OpRef),
+ _ = flush_abort_msg(SockRef, OpRef),
+ ok;
+ {error, not_found} ->
+ _ = flush_abort_msg(SockRef, OpRef),
+ {error, einval};
+ Other ->
+ _ = flush_abort_msg(SockRef, OpRef),
+ Other
+ end.
+
+flush_select_msg(SockRef, Ref) ->
+ receive
+ ?mk_socket_msg(?mk_socket(SockRef), select, Ref) ->
+ ok
+ after 0 ->
+ ok
+ end.
+
+flush_abort_msg(SockRef, Ref) ->
+ receive
+ ?mk_socket_msg(?mk_socket(SockRef), abort, {Ref, Reason}) ->
+ Reason
+ after 0 ->
+ ok
+ end.
+
+
+%% ===========================================================================
+%%
+%% Misc utility functions
+%%
+%% ===========================================================================
+
+%% formated_timestamp() ->
+%% format_timestamp(os:timestamp()).
+
+%% format_timestamp(Now) ->
+%% N2T = fun(N) -> calendar:now_to_local_time(N) end,
+%% format_timestamp(Now, N2T, true).
+
+%% format_timestamp({_N1, _N2, N3} = N, N2T, true) ->
+%% FormatExtra = ".~.2.0w",
+%% ArgsExtra = [N3 div 10000],
+%% format_timestamp(N, N2T, FormatExtra, ArgsExtra);
+%% format_timestamp({_N1, _N2, _N3} = N, N2T, false) ->
+%% FormatExtra = "",
+%% ArgsExtra = [],
+%% format_timestamp(N, N2T, FormatExtra, ArgsExtra).
+
+%% format_timestamp(N, N2T, FormatExtra, ArgsExtra) ->
+%% {Date, Time} = N2T(N),
+%% {YYYY,MM,DD} = Date,
+%% {Hour,Min,Sec} = Time,
+%% FormatDate =
+%% io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w" ++ FormatExtra,
+%% [YYYY, MM, DD, Hour, Min, Sec] ++ ArgsExtra),
+%% lists:flatten(FormatDate).
+
+
+deadline(Timeout) ->
+ case Timeout of
+ nowait ->
+ Timeout;
+ infinity ->
+ Timeout;
+ 0 ->
+ zero;
+ _ when is_integer(Timeout), 0 < Timeout ->
+ timestamp() + Timeout;
+ _ ->
+ badarg
+ end.
+
+timeout(Deadline) ->
+ case Deadline of
+ infinity ->
+ Deadline;
+ zero ->
+ 0;
+ _ ->
+ Now = timestamp(),
+ if
+ Deadline > Now ->
+ Deadline - Now;
+ true ->
+ 0
+ end
+ end.
+
+timestamp() ->
+ erlang:monotonic_time(milli_seconds).
+
+
+-compile({inline, [bincat/2]}).
+bincat(<<>>, <<_/binary>> = B) -> B;
+bincat(<<_/binary>> = A, <<>>) -> A;
+bincat(<<_/binary>> = A, <<_/binary>> = B) ->
+ <<A/binary, B/binary>>.
+
+
+%% p(F) ->
+%% p(F, []).
+
+%% p(F, A) ->
+%% p(get(sname), F, A).
+
+%% p(undefined, F, A) ->
+%% p("***", F, A);
+%% p(SName, F, A) ->
+%% TS = formated_timestamp(),
+%% io:format(user,"[~s][~s,~p] " ++ F ++ "~n", [TS, SName, self()|A]),
+%% io:format("[~s][~s,~p] " ++ F ++ "~n", [TS, SName, self()|A]).
diff --git a/lib/kernel/src/user_drv.erl b/lib/kernel/src/user_drv.erl
index 3ef6bc5533..f420b4faea 100644
--- a/lib/kernel/src/user_drv.erl
+++ b/lib/kernel/src/user_drv.erl
@@ -121,6 +121,13 @@ server1(Iport, Oport, Shell) ->
case init:get_argument(remsh) of
{ok,[[Node]]} ->
ANode = list_to_atom(append_hostname(Node)),
+ %% We try to connect to the node if the current node is not
+ %% a distributed node yet. If this succeeds it means that we
+ %% are running using "-sname undefined".
+ [begin
+ _ = net_kernel:start([undefined, shortnames]),
+ net_kernel:connect_node(ANode)
+ end || node() =:= nonode@nohost],
RShell = {ANode,shell,start,[]},
RGr = group:start(self(), RShell, rem_sh_opts(ANode)),
{RGr,RShell};
diff --git a/lib/kernel/test/Makefile b/lib/kernel/test/Makefile
index dcc892ec50..6a3696f92e 100644
--- a/lib/kernel/test/Makefile
+++ b/lib/kernel/test/Makefile
@@ -24,6 +24,21 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk
# Target Specs
# ----------------------------------------------------
+SOCKET_MODULES = \
+ socket_test_lib \
+ socket_test_logger \
+ socket_test_evaluator \
+ socket_test_ttest_lib \
+ socket_test_ttest_tcp_gen \
+ socket_test_ttest_tcp_socket \
+ socket_test_ttest_tcp_client \
+ socket_test_ttest_tcp_client_gen \
+ socket_test_ttest_tcp_client_socket \
+ socket_test_ttest_tcp_server \
+ socket_test_ttest_tcp_server_gen \
+ socket_test_ttest_tcp_server_socket \
+ socket_SUITE
+
MODULES= \
erpc_SUITE \
rpc_SUITE \
@@ -89,6 +104,7 @@ MODULES= \
pg_SUITE \
pg2_SUITE \
seq_trace_SUITE \
+ $(SOCKET_MODULES) \
wrap_log_reader_SUITE \
cleanup \
ignore_cores \
@@ -113,6 +129,10 @@ APP_FILES = \
topApp3.app
ERL_FILES= $(MODULES:%=%.erl) code_a_test.erl
+HRL_FILES= \
+ socket_test_evaluator.hrl \
+ socket_test_ttest.hrl \
+ socket_test_ttest_client.hrl
TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR))
INSTALL_PROGS= $(TARGET_FILES)
@@ -135,6 +155,7 @@ ERL_COMPILE_FLAGS +=
EBIN = .
TARGETS = $(MODULES:%=$(EBIN)/%.$(EMULATOR))
+SOCKET_TARGETS = $(SOCKET_MODULES:%=$(EBIN)/%.$(EMULATOR))
# ----------------------------------------------------
@@ -161,6 +182,7 @@ clean:
docs:
targets: $(TARGETS)
+socket_targets: $(SOCKET_TARGETS)
# ----------------------------------------------------
@@ -172,7 +194,7 @@ release_spec: opt
release_tests_spec: make_emakefile
$(INSTALL_DIR) "$(RELSYSDIR)"
- $(INSTALL_DATA) $(ERL_FILES) "$(RELSYSDIR)"
+ $(INSTALL_DATA) $(ERL_FILES) $(HRL_FILES) "$(RELSYSDIR)"
$(INSTALL_DATA) $(APP_FILES) "$(RELSYSDIR)"
$(INSTALL_DATA) \
kernel.spec kernel_smoke.spec kernel_bench.spec logger.spec \
diff --git a/lib/kernel/test/erl_boot_server_SUITE.erl b/lib/kernel/test/erl_boot_server_SUITE.erl
index 1eaa2cf500..491176678a 100644
--- a/lib/kernel/test/erl_boot_server_SUITE.erl
+++ b/lib/kernel/test/erl_boot_server_SUITE.erl
@@ -238,7 +238,7 @@ responses(Config) when is_list(Config) ->
{ok,BootPid} = erl_boot_server:start_link([Host]),
%% Send junk
- S1 = open_udp(),
+ S1 = open_udp(Ip),
prim_inet:sendto(S1, Ip, EBOOT_PORT, ["0"]),
receive
What ->
@@ -249,10 +249,10 @@ responses(Config) when is_list(Config) ->
end,
%% Req from a slave with same erlang vsn.
- S2 = open_udp(),
+ S2 = open_udp(Ip),
prim_inet:sendto(S2, Ip, EBOOT_PORT, [EBOOT_REQUEST,ThisVer]),
receive
- {udp,S2,Ip,_Port1,Resp1} ->
+ {udp,S2,_Ip,_Port1,Resp1} ->
close_udp(S2),
EBOOT_REPLY = string:substr(Resp1, 1, length(EBOOT_REPLY)),
Rest1 = string:substr(Resp1, length(EBOOT_REPLY)+1, length(Resp1)),
@@ -263,7 +263,7 @@ responses(Config) when is_list(Config) ->
end,
%% Req from a slave with other erlang vsn.
- S3 = open_udp(),
+ S3 = open_udp(Ip),
prim_inet:sendto(S3, Ip, EBOOT_PORT, [EBOOT_REQUEST,"1.0"]),
receive
Anything ->
@@ -284,7 +284,7 @@ responses(Config) when is_list(Config) ->
{ok,BootPid2} = erl_boot_server:start_link(["127.0.0.1"]),
%% Req from slave with invalid ip address.
- S4 = open_udp(),
+ S4 = open_udp(Ip),
Ret =
case Ip of
{127,0,0,1} ->
@@ -336,11 +336,11 @@ good_hosts(_Config) ->
GoodHost3 = "sauron",
[GoodHost1, GoodHost2, GoodHost3].
-open_udp() ->
+open_udp(Ip) ->
{ok, S} = prim_inet:open(udp, inet, dgram),
ok = prim_inet:setopts(S, [{mode,list},{active,true},
{deliver,term},{broadcast,true}]),
- {ok,_} = prim_inet:bind(S, {0,0,0,0}, 0),
+ {ok,_} = prim_inet:bind(S, Ip, 0),
S.
close_udp(S) ->
diff --git a/lib/kernel/test/file_name_SUITE.erl b/lib/kernel/test/file_name_SUITE.erl
index 91dce39d12..7166064558 100644
--- a/lib/kernel/test/file_name_SUITE.erl
+++ b/lib/kernel/test/file_name_SUITE.erl
@@ -634,9 +634,9 @@ hopeless_darwin() ->
case {os:type(),os:version()} of
{{unix,darwin},{Major,_,_}} ->
%% icky file names worked between 10 and 17, but started returning
- %% EILSEQ in 18. The check against 18 is exact in case newer
+ %% EILSEQ in 18. The check against 18..19 is exact in case newer
%% versions of Darwin support them again.
- Major < 9 orelse Major =:= 18;
+ Major < 9 orelse (Major >= 18 andalso Major =< 19);
_ ->
false
end.
diff --git a/lib/kernel/test/gen_sctp_SUITE.erl b/lib/kernel/test/gen_sctp_SUITE.erl
index 65183d83cc..55109a5178 100644
--- a/lib/kernel/test/gen_sctp_SUITE.erl
+++ b/lib/kernel/test/gen_sctp_SUITE.erl
@@ -41,7 +41,8 @@
peeloff_active_once/1, peeloff_active_true/1, peeloff_active_n/1,
buffers/1,
names_unihoming_ipv4/1, names_unihoming_ipv6/1,
- names_multihoming_ipv4/1, names_multihoming_ipv6/1]).
+ names_multihoming_ipv4/1, names_multihoming_ipv6/1,
+ recv_close/1]).
suite() ->
[{ct_hooks,[ts_install_cth]},
@@ -56,10 +57,25 @@ all() ->
{group,G}].
groups() ->
- [{smoke,[],[basic,basic_stream]},
- {old_solaris,[],[skip_old_solaris]},
- {extensive,[],
- [api_open_close, api_listen, api_connect_init,
+ [
+ {smoke, [], smoke_cases()},
+ {old_solaris, [], old_solaris_cases()},
+ {extensive, [], extensive_cases()}
+ ].
+
+smoke_cases() ->
+ [
+ basic,
+ basic_stream
+ ].
+
+old_solaris_cases() ->
+ [
+ skip_old_solaris
+ ].
+
+extensive_cases() ->
+ [api_open_close, api_listen, api_connect_init,
api_opts, xfer_min, xfer_active, def_sndrcvinfo, implicit_inet6,
open_multihoming_ipv4_socket,
open_unihoming_ipv6_socket,
@@ -68,7 +84,8 @@ groups() ->
xfer_stream_min, peeloff_active_once,
peeloff_active_true, peeloff_active_n, buffers,
names_unihoming_ipv4, names_unihoming_ipv6,
- names_multihoming_ipv4, names_multihoming_ipv6]}].
+ names_multihoming_ipv4, names_multihoming_ipv6,
+ recv_close].
init_per_suite(_Config) ->
case gen_sctp:open() of
@@ -1511,6 +1528,111 @@ recv_comm_up_eventually(S) ->
recv_comm_up_eventually(S)
end.
+
+%%
+recv_close(Config) when is_list(Config) ->
+ p("create server socket (and listen)"),
+ {ok, S} = gen_sctp:open(),
+ gen_sctp:listen(S, true),
+ {ok, SPort} = inet:port(S),
+
+ p("create client socket (and connect)"),
+ {ok, C} = gen_sctp:open(),
+ {ok, _} = gen_sctp:connect(C, localhost, SPort, []),
+
+ TC = self(),
+ RECV = fun() ->
+ p("try setup recv(s)"),
+ ok = recv_close_setup_recv(S),
+ p("announce ready"),
+ TC ! {self(), ready},
+ p("try data recv"),
+ Res = gen_sctp:recv(S),
+ p("recv res: "
+ "~n ~p", [Res]),
+ exit(Res)
+ end,
+ p("spawn reader - then await reader ready"),
+ {Pid, MRef} = spawn_monitor(RECV),
+ receive
+ {'DOWN', MRef, process, Pid, PreReason} ->
+ %% Make sure it does not die for some other reason...
+ p("unexpected reader termination:"
+ "~n ~p", [PreReason]),
+ (catch gen_sctp:close(S)),
+ (catch gen_sctp:close(C)),
+ ?line ct:fail("Unexpected pre close from reader (~p): ~p",
+ [Pid, PreReason]);
+ {Pid, ready} ->
+ p("reader ready"),
+ ok
+ after 30000 -> % Just in case...
+ %% This is **extreme**, but there is no way to know
+ %% how long it will take to iterate through all the
+ %% addresses of a host...
+ p("reader ready timeout"),
+ (catch gen_sctp:close(S)),
+ (catch gen_sctp:close(C)),
+ ?line ct:fail("Unexpected pre close timeout (~p)", [Pid])
+ end,
+
+ p("\"ensure\" reader reading..."),
+ receive
+ Any ->
+ p("Received unexpected message: "
+ "~n ~p", [Any]),
+ (catch gen_sctp:close(S)),
+ (catch gen_sctp:close(C)),
+ ?line ct:fail("Unexpected message: ~p", [Any])
+ after 5000 ->
+ ok
+ end,
+
+ p("close server socket"),
+ ok = gen_sctp:close(S),
+ p("await reader termination"),
+ receive
+ {'DOWN', MRef, process, Pid, {error, closed}} ->
+ p("expected reader termination result"),
+ (catch gen_sctp:close(C)),
+ ok;
+ {'DOWN', MRef, process, Pid, PostReason} ->
+ p("unexpected reader termination: "
+ "~n ~p", [PostReason]),
+ (catch gen_sctp:close(C)),
+ ?line ct:fail("Unexpected post close from reader (~p): ~p",
+ [Pid, PostReason])
+ after 5000 ->
+ p("unexpected reader termination timeout"),
+ demonitor(MRef, [flush]),
+ (catch gen_sctp:close(C)),
+ exit(Pid, kill),
+ ?line ct:fail("Reader (~p) termination timeout", [Pid])
+ end,
+ p("close client socket"),
+ (catch gen_sctp:close(C)),
+ p("done"),
+ ok.
+
+
+recv_close_setup_recv(S) ->
+ recv_close_setup_recv(S, 1).
+
+recv_close_setup_recv(S, N) ->
+ p("try setup recv ~w", [N]),
+ case gen_sctp:recv(S, 5000) of
+ {ok, {Addr,
+ Port,
+ _AncData,
+ Data}} when is_tuple(Addr) andalso is_integer(Port) ->
+ p("setup recv ~w: "
+ "~n ~p", [N, Data]),
+ recv_close_setup_recv(S, N+1);
+ {error, timeout} ->
+ ok
+ end.
+
+
%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% socket gen_server ultra light
@@ -1745,3 +1867,21 @@ match_unless_solaris(A, B) ->
timestamp() ->
erlang:monotonic_time().
+
+formated_timestamp() ->
+ format_timestamp(os:timestamp()).
+
+format_timestamp({_N1, _N2, N3} = TS) ->
+ {_Date, Time} = calendar:now_to_local_time(TS),
+ {Hour, Min, Sec} = Time,
+ FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w.~.3.0w",
+ [Hour, Min, Sec, N3 div 1000]),
+ lists:flatten(FormatTS).
+
+p(F) ->
+ p(F, []).
+
+p(F, A) ->
+ io:format("~s ~p " ++ F ++ "~n", [formated_timestamp(), self() | A]).
+
+
diff --git a/lib/kernel/test/gen_tcp_echo_SUITE.erl b/lib/kernel/test/gen_tcp_echo_SUITE.erl
index 1cf67a374a..e2b17d374b 100644
--- a/lib/kernel/test/gen_tcp_echo_SUITE.erl
+++ b/lib/kernel/test/gen_tcp_echo_SUITE.erl
@@ -246,6 +246,16 @@ echo_packet0(Echo, Type, EchoFun, SlowEcho, Opts) ->
echo_packet1(Echo, Type, EchoFun, infinite);
true -> ok
end,
+ PacketSize =:= 0 andalso
+ begin
+ %% Switch to raw mode and echo one byte
+ ok = inet:setopts(Echo, [{packet, raw}, {active, false}]),
+ ok = gen_tcp:send(Echo, <<"$">>),
+ case gen_tcp:recv(Echo, 1) of
+ {ok, <<"$">>} -> ok;
+ {ok, "$"} -> ok
+ end
+ end,
_CloseResult = gen_tcp:close(Echo),
ct:log("echo_packet0[~w] close: ~p", [self(), _CloseResult]),
ok.
diff --git a/lib/kernel/test/gen_udp_SUITE.erl b/lib/kernel/test/gen_udp_SUITE.erl
index 2720d3cc77..7ec553ec51 100644
--- a/lib/kernel/test/gen_udp_SUITE.erl
+++ b/lib/kernel/test/gen_udp_SUITE.erl
@@ -40,25 +40,45 @@
recvtos/1, recvtosttl/1, recvttl/1, recvtclass/1,
sendtos/1, sendtosttl/1, sendttl/1, sendtclass/1,
local_basic/1, local_unbound/1,
- local_fdopen/1, local_fdopen_unbound/1, local_abstract/1]).
+ local_fdopen/1, local_fdopen_unbound/1, local_abstract/1,
+ recv_close/1]).
suite() ->
[{ct_hooks,[ts_install_cth]},
{timetrap,{minutes,1}}].
all() ->
- [send_to_closed, buffer_size, binary_passive_recv, max_buffer_size,
- bad_address, read_packets, recv_poll_after_active_once,
- open_fd, connect,
- implicit_inet6, active_n,
+ [
+ send_to_closed,
+ buffer_size,
+ binary_passive_recv,
+ max_buffer_size,
+ bad_address,
+ read_packets,
+ recv_poll_after_active_once,
+ open_fd,
+ connect,
+ implicit_inet6,
+ active_n,
recvtos, recvtosttl, recvttl, recvtclass,
sendtos, sendtosttl, sendttl, sendtclass,
- {group, local}].
+ {group, local},
+ recv_close
+ ].
groups() ->
- [{local, [],
- [local_basic, local_unbound,
- local_fdopen, local_fdopen_unbound, local_abstract]}].
+ [
+ {local, [], local_cases()}
+ ].
+
+local_cases() ->
+ [
+ local_basic,
+ local_unbound,
+ local_fdopen,
+ local_fdopen_unbound,
+ local_abstract
+ ].
init_per_suite(Config) ->
Config.
@@ -969,6 +989,52 @@ local_handshake(S, SAddr, C, CAddr) ->
end.
+
+
+%%-------------------------------------------------------------
+%% Open a passive socket. Create a socket that reads from it.
+%% Then close the socket.
+recv_close(Config) when is_list(Config) ->
+ {ok, Sock} = gen_udp:open(0, [{active, false}]),
+ RECV = fun() ->
+ io:format("~p try recv~n", [self()]),
+ Res = gen_udp:recv(Sock, 0),
+ io:format("~p recv res: ~p~n", [self(), Res]),
+ exit(Res)
+ end,
+ io:format("~p spawn reader", [self()]),
+ {Pid, MRef} = spawn_monitor(RECV),
+ receive
+ {'DOWN', MRef, process, Pid, PreReason} ->
+ %% Make sure id does not die for some other reason...
+ ?line ct:fail("Unexpected pre close from reader (~p): ~p",
+ [Pid, PreReason])
+ after 5000 -> % Just in case...
+ ok
+ end,
+ io:format("~p close socket", [self()]),
+ ok = gen_udp:close(Sock),
+ io:format("~p await reader termination", [self()]),
+ receive
+ {'DOWN', MRef, process, Pid, {error, closed}} ->
+ io:format("~p expected reader termination result", [self()]),
+ ok;
+ {'DOWN', MRef, process, Pid, PostReason} ->
+ io:format("~p unexpected reader termination: ~p",
+ [self(), PostReason]),
+ ?line ct:fail("Unexpected post close from reader (~p): ~p",
+ [Pid, PostReason])
+ after 5000 ->
+ io:format("~p unexpected reader termination timeout", [self()]),
+ demonitor(MRef, [flush]),
+ exit(Pid, kill),
+ ?line ct:fail("Reader (~p) termination timeout", [Pid])
+ end,
+ ok.
+
+
+
+
%%
%% Utils
%%
diff --git a/lib/kernel/test/global_SUITE.erl b/lib/kernel/test/global_SUITE.erl
index 3c4654b44c..22db756d97 100644
--- a/lib/kernel/test/global_SUITE.erl
+++ b/lib/kernel/test/global_SUITE.erl
@@ -3738,13 +3738,17 @@ start_node(Name, How, Config) ->
start_node(Name0, How, Args, Config) ->
Name = node_name(Name0, Config),
Pa = filename:dirname(code:which(?MODULE)),
- R = test_server:start_node(Name, How, [{args,
- Args ++ " " ++
- "-kernel net_setuptime 100 "
- %% "-noshell "
- "-pa " ++ Pa},
- {linked, false}
- ]),
+ R = test_server:start_node(
+ Name, How, [{args,
+ Args ++
+ " -kernel net_setuptime 100 " ++
+ %% Limit the amount of threads so that we
+ %% don't run into the maximum allowed
+ " +S 1 +SDio 1 " ++
+ %% "-noshell "
+ "-pa " ++ Pa},
+ {linked, false}
+ ]),
%% {linked,false} only seems to work for slave nodes.
%% ct:sleep(1000),
record_started_node(R).
@@ -3761,12 +3765,16 @@ start_node_rel(Name0, Rel, Config) ->
end,
Env = [],
Pa = filename:dirname(code:which(?MODULE)),
- Res = test_server:start_node(Name, peer,
- [{args,
- Compat ++
- " -kernel net_setuptime 100 "
- " -pa " ++ Pa},
- {erl, Release}] ++ Env),
+ Res = test_server:start_node(
+ Name, peer,
+ [{args,
+ Compat ++
+ " -kernel net_setuptime 100 " ++
+ %% Limit the amount of threads so that we
+ %% don't run into the maximum allowed
+ " +S 1 +SDio 1 " ++
+ "-pa " ++ Pa},
+ {erl, Release}] ++ Env),
record_started_node(Res).
record_started_node({ok, Node}) ->
diff --git a/lib/kernel/test/init_SUITE.erl b/lib/kernel/test/init_SUITE.erl
index ceee387289..47e44200bf 100644
--- a/lib/kernel/test/init_SUITE.erl
+++ b/lib/kernel/test/init_SUITE.erl
@@ -354,11 +354,18 @@ restart_with_mode(Config) when is_list(Config) ->
{ok,[[Erl]]} = init:get_argument(progname),
ModPath = filename:dirname(code:which(?MODULE)),
- Eval1 = "'Mode=code:get_mode(), io:fwrite(Mode), case Mode of interactive -> init:restart([{mode,embedded}]); embedded -> erlang:halt() end'",
+ Quote = case os:type() of
+ {win32,_} ->
+ [$"];
+ {unix,_} ->
+ [$']
+ end,
+
+ Eval1 = Quote ++ "Mode=code:get_mode(), io:fwrite(Mode), case Mode of interactive -> init:restart([{mode,embedded}]); embedded -> erlang:halt() end" ++ Quote,
Cmd1 = Erl ++ " -mode interactive -noshell -eval " ++ Eval1,
"interactiveembedded" = os:cmd(Cmd1),
- Eval2 = "'Mode=code:get_mode(), io:fwrite(Mode), case Mode of embedded -> init:restart([{mode,interactive}]); interactive -> erlang:halt() end'",
+ Eval2 = Quote ++ "Mode=code:get_mode(), io:fwrite(Mode), case Mode of embedded -> init:restart([{mode,interactive}]); interactive -> erlang:halt() end" ++ Quote,
Cmd2 = Erl ++ " -mode embedded -noshell -eval " ++ Eval2,
"embeddedinteractive" = os:cmd(Cmd2),
diff --git a/lib/kernel/test/logger_std_h_SUITE.erl b/lib/kernel/test/logger_std_h_SUITE.erl
index d65305384f..8ba2f946e7 100644
--- a/lib/kernel/test/logger_std_h_SUITE.erl
+++ b/lib/kernel/test/logger_std_h_SUITE.erl
@@ -291,9 +291,16 @@ errors(Config) ->
_ ->
NoDir = lists:concat(["/",?MODULE,"_dir"]),
{error,
- {handler_not_added,{open_failed,NoDir,eacces}}} =
+ {handler_not_added,{open_failed,NoDir,Error}}} =
logger:add_handler(myh2,logger_std_h,
- #{config=>#{type=>{file,NoDir}}})
+ #{config=>#{type=>{file,NoDir}}}),
+ case Error of
+ erofs ->
+ %% Happens on OS X
+ ok;
+ eacces ->
+ ok
+ end
end,
{error,
@@ -1768,7 +1775,7 @@ rotation_opts_restart_handler(Config) ->
HConfig3#{config=>StdHConfig3#{max_no_bytes=>75,
max_no_files=>1,
compress_on_rotate=>true}}),
- timer:sleep(100),
+ timer:sleep(500),
{ok,#file_info{size=0}} = file:read_file_info(Log),
{ok,#file_info{size=29}} = file:read_file_info(Log++".0.gz"),
[_] = filelib:wildcard(Log++".*"),
diff --git a/lib/kernel/test/net_SUITE.erl b/lib/kernel/test/net_SUITE.erl
index b3226f7e8c..5e6ee054f7 100644
--- a/lib/kernel/test/net_SUITE.erl
+++ b/lib/kernel/test/net_SUITE.erl
@@ -132,18 +132,13 @@ api_basic_cases() ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
init_per_suite(Config) ->
- %% We test on the socket module for simplicity
- case lists:member(socket, erlang:loaded()) of
- true ->
- case os:type() of
- {win32, _} ->
- not_yet_implemented();
- _ ->
- %% ?LOGGER:start(),
- Config
- end;
- false ->
- {skip, "esock disabled"}
+ try net:info() of
+ #{} ->
+ %% ?LOGGER:start(),
+ Config
+ catch
+ error : notsup ->
+ {skip, "esock not supported"}
end.
end_per_suite(_) ->
diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl
new file mode 100644
index 0000000000..7dd9b04662
--- /dev/null
+++ b/lib/kernel/test/socket_SUITE.erl
@@ -0,0 +1,44297 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-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%
+%%
+
+%% There are some environment variables that can be used to "manipulate"
+%% the test suite:
+%%
+%% Variable that controls which 'groups' are to run (with default values)
+%%
+%% ESOCK_TEST_API: include
+%% ESOCK_TEST_SOCK_CLOSE: include
+%% ESOCK_TEST_TRAFFIC: include
+%% ESOCK_TEST_TTEST: exclude
+%%
+%% Variable that controls "verbosity" of the test case(s):
+%%
+%% ESOCK_TEST_QUIET: true (default) | false
+%%
+%% Defines the runtime of the ttest cases
+%% (This is the time during which "measurement" is performed.
+%% the actual time it takes for the test case to complete
+%% will be longer; setup, completion, ...)
+%%
+%% ESOCK_TEST_TTEST_RUNTIME: 10 seconds
+%% Format of values: <integer>[<unit>]
+%% Where unit is: ms | s | m
+%% ms - milli seconds
+%% s - seconds (default)
+%% m - minutes
+%%
+
+%% Run the entire test suite:
+%% ts:run(emulator, socket_SUITE, [batch]).
+%%
+%% Run a specific group:
+%% ts:run(emulator, socket_SUITE, {group, foo}, [batch]).
+%%
+%% Run a specific test case:
+%% ts:run(emulator, socket_SUITE, foo, [batch]).
+
+-module(socket_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("common_test/include/ct_event.hrl").
+-include("socket_test_evaluator.hrl").
+
+%% Suite exports
+-export([suite/0, all/0, groups/0]).
+-export([init_per_suite/1, end_per_suite/1,
+ init_per_group/2, end_per_group/2,
+ init_per_testcase/2, end_per_testcase/2]).
+
+%% Test cases
+-export([
+ %% *** API Misc ***
+ api_m_info/1,
+ api_m_debug/1,
+
+ %% *** API Basic ***
+ api_b_open_and_info_udp4/1,
+ api_b_open_and_info_udp6/1,
+ api_b_open_and_info_tcp4/1,
+ api_b_open_and_info_tcp6/1,
+ api_b_open_and_close_udp4/1,
+ api_b_open_and_close_udp6/1,
+ api_b_open_and_close_tcp4/1,
+ api_b_open_and_close_tcp6/1,
+ api_b_open_and_close_udpL/1,
+ api_b_open_and_close_tcpL/1,
+ api_b_open_and_close_seqpL/1,
+ api_b_open_and_close_sctp4/1,
+ api_b_open_and_maybe_close_raw/1,
+ api_b_sendto_and_recvfrom_udp4/1,
+ api_b_sendto_and_recvfrom_udpL/1,
+ api_b_sendmsg_and_recvmsg_udp4/1,
+ api_b_sendmsg_and_recvmsg_udpL/1,
+ api_b_send_and_recv_tcp4/1,
+ api_b_send_and_recv_tcpL/1,
+ api_b_send_and_recv_seqpL/1,
+ api_b_sendmsg_and_recvmsg_tcp4/1,
+ api_b_sendmsg_and_recvmsg_tcpL/1,
+ api_b_sendmsg_and_recvmsg_seqpL/1,
+ api_b_sendmsg_and_recvmsg_sctp4/1,
+
+ %% *** API socket from FD ***
+ api_ffd_open_wod_and_info_udp4/1,
+ api_ffd_open_wod_and_info_udp6/1,
+ api_ffd_open_wod_and_info_tcp4/1,
+ api_ffd_open_wod_and_info_tcp6/1,
+ api_ffd_open_wd_and_info_udp4/1,
+ api_ffd_open_wd_and_info_udp6/1,
+ api_ffd_open_wd_and_info_tcp4/1,
+ api_ffd_open_wd_and_info_tcp6/1,
+ api_ffd_open_and_open_wod_and_send_udp4/1,
+ api_ffd_open_and_open_wod_and_send_udp6/1,
+ api_ffd_open_and_open_wd_and_send_udp4/1,
+ api_ffd_open_and_open_wd_and_send_udp6/1,
+ api_ffd_open_connect_and_open_wod_and_send_tcp4/1,
+ api_ffd_open_connect_and_open_wod_and_send_tcp6/1,
+ api_ffd_open_connect_and_open_wd_and_send_tcp4/1,
+ api_ffd_open_connect_and_open_wd_and_send_tcp6/1,
+
+
+ %% *** API async ***
+ api_a_connect_tcp4/1,
+ api_a_connect_tcp6/1,
+ api_a_sendto_and_recvfrom_udp4/1,
+ api_a_sendto_and_recvfrom_udp6/1,
+ api_a_sendmsg_and_recvmsg_udp4/1,
+ api_a_sendmsg_and_recvmsg_udp6/1,
+ api_a_send_and_recv_tcp4/1,
+ api_a_send_and_recv_tcp6/1,
+ api_a_sendmsg_and_recvmsg_tcp4/1,
+ api_a_sendmsg_and_recvmsg_tcp6/1,
+ api_a_recvfrom_cancel_udp4/1,
+ api_a_recvfrom_cancel_udp6/1,
+ api_a_recvmsg_cancel_udp4/1,
+ api_a_recvmsg_cancel_udp6/1,
+ api_a_accept_cancel_tcp4/1,
+ api_a_accept_cancel_tcp6/1,
+ api_a_recv_cancel_tcp4/1,
+ api_a_recv_cancel_tcp6/1,
+ api_a_recvmsg_cancel_tcp4/1,
+ api_a_recvmsg_cancel_tcp6/1,
+ api_a_mrecvfrom_cancel_udp4/1,
+ api_a_mrecvfrom_cancel_udp6/1,
+ api_a_mrecvmsg_cancel_udp4/1,
+ api_a_mrecvmsg_cancel_udp6/1,
+ api_a_maccept_cancel_tcp4/1,
+ api_a_maccept_cancel_tcp6/1,
+ api_a_mrecv_cancel_tcp4/1,
+ api_a_mrecv_cancel_tcp6/1,
+ api_a_mrecvmsg_cancel_tcp4/1,
+ api_a_mrecvmsg_cancel_tcp6/1,
+
+
+ %% *** API Options ***
+ api_opt_simple_otp_options/1,
+ api_opt_simple_otp_meta_option/1,
+ api_opt_simple_otp_rcvbuf_option/1,
+ api_opt_simple_otp_controlling_process/1,
+ api_opt_sock_acceptconn_udp/1,
+ api_opt_sock_acceptconn_tcp/1,
+ api_opt_sock_acceptfilter/1,
+ api_opt_sock_bindtodevice/1,
+ api_opt_sock_broadcast/1,
+ api_opt_sock_debug/1,
+ api_opt_sock_domain/1,
+ api_opt_sock_dontroute/1,
+ api_opt_sock_error/1,
+ api_opt_sock_keepalive/1,
+ api_opt_sock_linger/1,
+ api_opt_sock_mark/1,
+ api_opt_sock_oobinline/1,
+ api_opt_sock_passcred_tcp4/1,
+ api_opt_sock_peek_off_tcpL/1,
+ api_opt_sock_peercred_tcpL/1,
+ api_opt_sock_priority_udp4/1,
+ api_opt_sock_priority_tcp4/1,
+ api_opt_sock_rcvbuf_udp4/1,
+ api_opt_sock_rcvlowat_udp4/1,
+ api_opt_sock_rcvtimeo_udp4/1,
+ api_opt_sock_sndbuf_udp4/1,
+ api_opt_sock_sndlowat_udp4/1,
+ api_opt_sock_sndtimeo_udp4/1,
+ api_opt_sock_timestamp_udp4/1,
+ api_opt_sock_timestamp_tcp4/1,
+ api_opt_ip_add_drop_membership/1,
+ api_opt_ip_pktinfo_udp4/1,
+ api_opt_ip_recvopts_udp4/1,
+ api_opt_ip_recvorigdstaddr_udp4/1,
+ api_opt_ip_recvtos_udp4/1,
+ api_opt_ip_recvttl_udp4/1,
+ api_opt_ip_tos_udp4/1,
+ api_opt_ip_recverr_udp4/1,
+ api_opt_ip_mopts_udp4/1,
+ api_opt_ipv6_recvpktinfo_udp6/1,
+ api_opt_ipv6_flowinfo_udp6/1,
+ api_opt_ipv6_hoplimit_udp6/1,
+ api_opt_ipv6_tclass_udp6/1,
+ api_opt_ipv6_recverr_udp6/1,
+ api_opt_ipv6_mopts_udp6/1,
+ api_opt_tcp_congestion_tcp4/1,
+ api_opt_tcp_cork_tcp4/1,
+ api_opt_tcp_maxseg_tcp4/1,
+ api_opt_tcp_nodelay_tcp4/1,
+ api_opt_udp_cork_udp4/1,
+
+ %% *** API Operation Timeout ***
+ api_to_connect_tcp4/1,
+ api_to_connect_tcp6/1,
+ api_to_accept_tcp4/1,
+ api_to_accept_tcp6/1,
+ api_to_maccept_tcp4/1,
+ api_to_maccept_tcp6/1,
+ api_to_send_tcp4/1,
+ api_to_send_tcp6/1,
+ api_to_sendto_udp4/1,
+ api_to_sendto_udp6/1,
+ api_to_sendmsg_tcp4/1,
+ api_to_sendmsg_tcp6/1,
+ api_to_recv_udp4/1,
+ api_to_recv_udp6/1,
+ api_to_recv_tcp4/1,
+ api_to_recv_tcp6/1,
+ api_to_recvfrom_udp4/1,
+ api_to_recvfrom_udp6/1,
+ api_to_recvmsg_udp4/1,
+ api_to_recvmsg_udp6/1,
+ api_to_recvmsg_tcp4/1,
+ api_to_recvmsg_tcp6/1,
+
+ %% Socket Registry
+ reg_s_single_open_and_close_and_count/1,
+
+ %% *** Socket Closure ***
+ sc_cpe_socket_cleanup_tcp4/1,
+ sc_cpe_socket_cleanup_tcp6/1,
+ sc_cpe_socket_cleanup_tcpL/1,
+ sc_cpe_socket_cleanup_udp4/1,
+ sc_cpe_socket_cleanup_udp6/1,
+ sc_cpe_socket_cleanup_udpL/1,
+
+ sc_lc_recv_response_tcp4/1,
+ sc_lc_recv_response_tcp6/1,
+ sc_lc_recv_response_tcpL/1,
+ sc_lc_recvfrom_response_udp4/1,
+ sc_lc_recvfrom_response_udp6/1,
+ sc_lc_recvfrom_response_udpL/1,
+ sc_lc_recvmsg_response_tcp4/1,
+ sc_lc_recvmsg_response_tcp6/1,
+ sc_lc_recvmsg_response_tcpL/1,
+ sc_lc_recvmsg_response_udp4/1,
+ sc_lc_recvmsg_response_udp6/1,
+ sc_lc_recvmsg_response_udpL/1,
+ sc_lc_acceptor_response_tcp4/1,
+ sc_lc_acceptor_response_tcp6/1,
+ sc_lc_acceptor_response_tcpL/1,
+
+ sc_rc_recv_response_tcp4/1,
+ sc_rc_recv_response_tcp6/1,
+ sc_rc_recv_response_tcpL/1,
+ sc_rc_recvmsg_response_tcp4/1,
+ sc_rc_recvmsg_response_tcp6/1,
+ sc_rc_recvmsg_response_tcpL/1,
+
+ sc_rs_recv_send_shutdown_receive_tcp4/1,
+ sc_rs_recv_send_shutdown_receive_tcp6/1,
+ sc_rs_recv_send_shutdown_receive_tcpL/1,
+ sc_rs_recvmsg_send_shutdown_receive_tcp4/1,
+ sc_rs_recvmsg_send_shutdown_receive_tcp6/1,
+ sc_rs_recvmsg_send_shutdown_receive_tcpL/1,
+
+ %% *** Traffic ***
+ traffic_send_and_recv_counters_tcp4/1,
+ traffic_send_and_recv_counters_tcp6/1,
+ traffic_send_and_recv_counters_tcpL/1,
+ traffic_sendmsg_and_recvmsg_counters_tcp4/1,
+ traffic_sendmsg_and_recvmsg_counters_tcp6/1,
+ traffic_sendmsg_and_recvmsg_counters_tcpL/1,
+ traffic_sendto_and_recvfrom_counters_udp4/1,
+ traffic_sendto_and_recvfrom_counters_udp6/1,
+ traffic_sendto_and_recvfrom_counters_udpL/1,
+ traffic_sendmsg_and_recvmsg_counters_udp4/1,
+ traffic_sendmsg_and_recvmsg_counters_udp6/1,
+ traffic_sendmsg_and_recvmsg_counters_udpL/1,
+
+ traffic_send_and_recv_chunks_tcp4/1,
+ traffic_send_and_recv_chunks_tcp6/1,
+ traffic_send_and_recv_chunks_tcpL/1,
+
+ traffic_ping_pong_small_send_and_recv_tcp4/1,
+ traffic_ping_pong_small_send_and_recv_tcp6/1,
+ traffic_ping_pong_small_send_and_recv_tcpL/1,
+ traffic_ping_pong_medium_send_and_recv_tcp4/1,
+ traffic_ping_pong_medium_send_and_recv_tcp6/1,
+ traffic_ping_pong_medium_send_and_recv_tcpL/1,
+ traffic_ping_pong_large_send_and_recv_tcp4/1,
+ traffic_ping_pong_large_send_and_recv_tcp6/1,
+ traffic_ping_pong_large_send_and_recv_tcpL/1,
+
+ traffic_ping_pong_small_sendto_and_recvfrom_udp4/1,
+ traffic_ping_pong_small_sendto_and_recvfrom_udp6/1,
+ traffic_ping_pong_small_sendto_and_recvfrom_udpL/1,
+ traffic_ping_pong_medium_sendto_and_recvfrom_udp4/1,
+ traffic_ping_pong_medium_sendto_and_recvfrom_udp6/1,
+ traffic_ping_pong_medium_sendto_and_recvfrom_udpL/1,
+
+ traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4/1,
+ traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6/1,
+ traffic_ping_pong_small_sendmsg_and_recvmsg_tcpL/1,
+ traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4/1,
+ traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6/1,
+ traffic_ping_pong_medium_sendmsg_and_recvmsg_tcpL/1,
+ traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4/1,
+ traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6/1,
+ traffic_ping_pong_large_sendmsg_and_recvmsg_tcpL/1,
+
+ traffic_ping_pong_small_sendmsg_and_recvmsg_udp4/1,
+ traffic_ping_pong_small_sendmsg_and_recvmsg_udp6/1,
+ traffic_ping_pong_small_sendmsg_and_recvmsg_udpL/1,
+ traffic_ping_pong_medium_sendmsg_and_recvmsg_udp4/1,
+ traffic_ping_pong_medium_sendmsg_and_recvmsg_udp6/1,
+ traffic_ping_pong_medium_sendmsg_and_recvmsg_udpL/1,
+
+ %% *** Time Test ***
+ %% Server: transport = gen_tcp, active = false
+ %% Client: transport = gen_tcp
+ ttest_sgenf_cgenf_small_tcp4/1,
+ ttest_sgenf_cgenf_small_tcp6/1,
+ ttest_sgenf_cgenf_medium_tcp4/1,
+ ttest_sgenf_cgenf_medium_tcp6/1,
+ ttest_sgenf_cgenf_large_tcp4/1,
+ ttest_sgenf_cgenf_large_tcp6/1,
+
+ ttest_sgenf_cgeno_small_tcp4/1,
+ ttest_sgenf_cgeno_small_tcp6/1,
+ ttest_sgenf_cgeno_medium_tcp4/1,
+ ttest_sgenf_cgeno_medium_tcp6/1,
+ ttest_sgenf_cgeno_large_tcp4/1,
+ ttest_sgenf_cgeno_large_tcp6/1,
+
+ ttest_sgenf_cgent_small_tcp4/1,
+ ttest_sgenf_cgent_small_tcp6/1,
+ ttest_sgenf_cgent_medium_tcp4/1,
+ ttest_sgenf_cgent_medium_tcp6/1,
+ ttest_sgenf_cgent_large_tcp4/1,
+ ttest_sgenf_cgent_large_tcp6/1,
+
+ %% Server: transport = gen_tcp, active = false
+ %% Client: transport = socket(tcp)
+ ttest_sgenf_csockf_small_tcp4/1,
+ ttest_sgenf_csockf_small_tcp6/1,
+ ttest_sgenf_csockf_medium_tcp4/1,
+ ttest_sgenf_csockf_medium_tcp6/1,
+ ttest_sgenf_csockf_large_tcp4/1,
+ ttest_sgenf_csockf_large_tcp6/1,
+
+ ttest_sgenf_csocko_small_tcp4/1,
+ ttest_sgenf_csocko_small_tcp6/1,
+ ttest_sgenf_csocko_medium_tcp4/1,
+ ttest_sgenf_csocko_medium_tcp6/1,
+ ttest_sgenf_csocko_large_tcp4/1,
+ ttest_sgenf_csocko_large_tcp6/1,
+
+ ttest_sgenf_csockt_small_tcp4/1,
+ ttest_sgenf_csockt_small_tcp6/1,
+ ttest_sgenf_csockt_medium_tcp4/1,
+ ttest_sgenf_csockt_medium_tcp6/1,
+ ttest_sgenf_csockt_large_tcp4/1,
+ ttest_sgenf_csockt_large_tcp6/1,
+
+ %% Server: transport = gen_tcp, active = once
+ %% Client: transport = gen_tcp
+ ttest_sgeno_cgenf_small_tcp4/1,
+ ttest_sgeno_cgenf_small_tcp6/1,
+ ttest_sgeno_cgenf_medium_tcp4/1,
+ ttest_sgeno_cgenf_medium_tcp6/1,
+ ttest_sgeno_cgenf_large_tcp4/1,
+ ttest_sgeno_cgenf_large_tcp6/1,
+
+ ttest_sgeno_cgeno_small_tcp4/1,
+ ttest_sgeno_cgeno_small_tcp6/1,
+ ttest_sgeno_cgeno_medium_tcp4/1,
+ ttest_sgeno_cgeno_medium_tcp6/1,
+ ttest_sgeno_cgeno_large_tcp4/1,
+ ttest_sgeno_cgeno_large_tcp6/1,
+
+ ttest_sgeno_cgent_small_tcp4/1,
+ ttest_sgeno_cgent_small_tcp6/1,
+ ttest_sgeno_cgent_medium_tcp4/1,
+ ttest_sgeno_cgent_medium_tcp6/1,
+ ttest_sgeno_cgent_large_tcp4/1,
+ ttest_sgeno_cgent_large_tcp6/1,
+
+ %% Server: transport = gen_tcp, active = once
+ %% Client: transport = socket(tcp)
+ ttest_sgeno_csockf_small_tcp4/1,
+ ttest_sgeno_csockf_small_tcp6/1,
+ ttest_sgeno_csockf_medium_tcp4/1,
+ ttest_sgeno_csockf_medium_tcp6/1,
+ ttest_sgeno_csockf_large_tcp4/1,
+ ttest_sgeno_csockf_large_tcp6/1,
+
+ ttest_sgeno_csocko_small_tcp4/1,
+ ttest_sgeno_csocko_small_tcp6/1,
+ ttest_sgeno_csocko_medium_tcp4/1,
+ ttest_sgeno_csocko_medium_tcp6/1,
+ ttest_sgeno_csocko_large_tcp4/1,
+ ttest_sgeno_csocko_large_tcp6/1,
+
+ ttest_sgeno_csockt_small_tcp4/1,
+ ttest_sgeno_csockt_small_tcp6/1,
+ ttest_sgeno_csockt_medium_tcp4/1,
+ ttest_sgeno_csockt_medium_tcp6/1,
+ ttest_sgeno_csockt_large_tcp4/1,
+ ttest_sgeno_csockt_large_tcp6/1,
+
+ %% Server: transport = gen_tcp, active = true
+ %% Client: transport = gen_tcp
+ ttest_sgent_cgenf_small_tcp4/1,
+ ttest_sgent_cgenf_small_tcp6/1,
+ ttest_sgent_cgenf_medium_tcp4/1,
+ ttest_sgent_cgenf_medium_tcp6/1,
+ ttest_sgent_cgenf_large_tcp4/1,
+ ttest_sgent_cgenf_large_tcp6/1,
+
+ ttest_sgent_cgeno_small_tcp4/1,
+ ttest_sgent_cgeno_small_tcp6/1,
+ ttest_sgent_cgeno_medium_tcp4/1,
+ ttest_sgent_cgeno_medium_tcp6/1,
+ ttest_sgent_cgeno_large_tcp4/1,
+ ttest_sgent_cgeno_large_tcp6/1,
+
+ ttest_sgent_cgent_small_tcp4/1,
+ ttest_sgent_cgent_small_tcp6/1,
+ ttest_sgent_cgent_medium_tcp4/1,
+ ttest_sgent_cgent_medium_tcp6/1,
+ ttest_sgent_cgent_large_tcp4/1,
+ ttest_sgent_cgent_large_tcp6/1,
+
+ %% Server: transport = gen_tcp, active = true
+ %% Client: transport = socket(tcp)
+ ttest_sgent_csockf_small_tcp4/1,
+ ttest_sgent_csockf_small_tcp6/1,
+ ttest_sgent_csockf_medium_tcp4/1,
+ ttest_sgent_csockf_medium_tcp6/1,
+ ttest_sgent_csockf_large_tcp4/1,
+ ttest_sgent_csockf_large_tcp6/1,
+
+ ttest_sgent_csocko_small_tcp4/1,
+ ttest_sgent_csocko_small_tcp6/1,
+ ttest_sgent_csocko_medium_tcp4/1,
+ ttest_sgent_csocko_medium_tcp6/1,
+ ttest_sgent_csocko_large_tcp4/1,
+ ttest_sgent_csocko_large_tcp6/1,
+
+ ttest_sgent_csockt_small_tcp4/1,
+ ttest_sgent_csockt_small_tcp6/1,
+ ttest_sgent_csockt_medium_tcp4/1,
+ ttest_sgent_csockt_medium_tcp6/1,
+ ttest_sgent_csockt_large_tcp4/1,
+ ttest_sgent_csockt_large_tcp6/1,
+
+ %% Server: transport = socket(tcp), active = false
+ %% Client: transport = gen_tcp
+ ttest_ssockf_cgenf_small_tcp4/1,
+ ttest_ssockf_cgenf_small_tcp6/1,
+ ttest_ssockf_cgenf_medium_tcp4/1,
+ ttest_ssockf_cgenf_medium_tcp6/1,
+ ttest_ssockf_cgenf_large_tcp4/1,
+ ttest_ssockf_cgenf_large_tcp6/1,
+
+ ttest_ssockf_cgeno_small_tcp4/1,
+ ttest_ssockf_cgeno_small_tcp6/1,
+ ttest_ssockf_cgeno_medium_tcp4/1,
+ ttest_ssockf_cgeno_medium_tcp6/1,
+ ttest_ssockf_cgeno_large_tcp4/1,
+ ttest_ssockf_cgeno_large_tcp6/1,
+
+ ttest_ssockf_cgent_small_tcp4/1,
+ ttest_ssockf_cgent_small_tcp6/1,
+ ttest_ssockf_cgent_medium_tcp4/1,
+ ttest_ssockf_cgent_medium_tcp6/1,
+ ttest_ssockf_cgent_large_tcp4/1,
+ ttest_ssockf_cgent_large_tcp6/1,
+
+ %% Server: transport = socket(tcp), active = false
+ %% Client: transport = socket(tcp)
+ ttest_ssockf_csockf_small_tcp4/1,
+ ttest_ssockf_csockf_small_tcp6/1,
+ ttest_ssockf_csockf_small_tcpL/1,
+ ttest_ssockf_csockf_medium_tcp4/1,
+ ttest_ssockf_csockf_medium_tcp6/1,
+ ttest_ssockf_csockf_medium_tcpL/1,
+ ttest_ssockf_csockf_large_tcp4/1,
+ ttest_ssockf_csockf_large_tcp6/1,
+ ttest_ssockf_csockf_large_tcpL/1,
+
+ ttest_ssockf_csocko_small_tcp4/1,
+ ttest_ssockf_csocko_small_tcp6/1,
+ ttest_ssockf_csocko_small_tcpL/1,
+ ttest_ssockf_csocko_medium_tcp4/1,
+ ttest_ssockf_csocko_medium_tcp6/1,
+ ttest_ssockf_csocko_medium_tcpL/1,
+ ttest_ssockf_csocko_large_tcp4/1,
+ ttest_ssockf_csocko_large_tcp6/1,
+ ttest_ssockf_csocko_large_tcpL/1,
+
+ ttest_ssockf_csockt_small_tcp4/1,
+ ttest_ssockf_csockt_small_tcp6/1,
+ ttest_ssockf_csockt_small_tcpL/1,
+ ttest_ssockf_csockt_medium_tcp4/1,
+ ttest_ssockf_csockt_medium_tcp6/1,
+ ttest_ssockf_csockt_medium_tcpL/1,
+ ttest_ssockf_csockt_large_tcp4/1,
+ ttest_ssockf_csockt_large_tcp6/1,
+ ttest_ssockf_csockt_large_tcpL/1,
+
+ %% Server: transport = socket(tcp), active = once
+ %% Client: transport = gen_tcp
+ ttest_ssocko_cgenf_small_tcp4/1,
+ ttest_ssocko_cgenf_small_tcp6/1,
+ ttest_ssocko_cgenf_medium_tcp4/1,
+ ttest_ssocko_cgenf_medium_tcp6/1,
+ ttest_ssocko_cgenf_large_tcp4/1,
+ ttest_ssocko_cgenf_large_tcp6/1,
+
+ ttest_ssocko_cgeno_small_tcp4/1,
+ ttest_ssocko_cgeno_small_tcp6/1,
+ ttest_ssocko_cgeno_medium_tcp4/1,
+ ttest_ssocko_cgeno_medium_tcp6/1,
+ ttest_ssocko_cgeno_large_tcp4/1,
+ ttest_ssocko_cgeno_large_tcp6/1,
+
+ ttest_ssocko_cgent_small_tcp4/1,
+ ttest_ssocko_cgent_small_tcp6/1,
+ ttest_ssocko_cgent_medium_tcp4/1,
+ ttest_ssocko_cgent_medium_tcp6/1,
+ ttest_ssocko_cgent_large_tcp4/1,
+ ttest_ssocko_cgent_large_tcp6/1,
+
+ %% Server: transport = socket(tcp), active = once
+ %% Client: transport = socket(tcp)
+ ttest_ssocko_csockf_small_tcp4/1,
+ ttest_ssocko_csockf_small_tcp6/1,
+ ttest_ssocko_csockf_small_tcpL/1,
+ ttest_ssocko_csockf_medium_tcp4/1,
+ ttest_ssocko_csockf_medium_tcpL/1,
+ ttest_ssocko_csockf_medium_tcp6/1,
+ ttest_ssocko_csockf_large_tcp4/1,
+ ttest_ssocko_csockf_large_tcp6/1,
+ ttest_ssocko_csockf_large_tcpL/1,
+
+ ttest_ssocko_csocko_small_tcp4/1,
+ ttest_ssocko_csocko_small_tcp6/1,
+ ttest_ssocko_csocko_small_tcpL/1,
+ ttest_ssocko_csocko_medium_tcp4/1,
+ ttest_ssocko_csocko_medium_tcp6/1,
+ ttest_ssocko_csocko_medium_tcpL/1,
+ ttest_ssocko_csocko_large_tcp4/1,
+ ttest_ssocko_csocko_large_tcp6/1,
+ ttest_ssocko_csocko_large_tcpL/1,
+
+ ttest_ssocko_csockt_small_tcp4/1,
+ ttest_ssocko_csockt_small_tcp6/1,
+ ttest_ssocko_csockt_small_tcpL/1,
+ ttest_ssocko_csockt_medium_tcp4/1,
+ ttest_ssocko_csockt_medium_tcp6/1,
+ ttest_ssocko_csockt_medium_tcpL/1,
+ ttest_ssocko_csockt_large_tcp4/1,
+ ttest_ssocko_csockt_large_tcp6/1,
+ ttest_ssocko_csockt_large_tcpL/1,
+
+ %% Server: transport = socket(tcp), active = true
+ %% Client: transport = gen_tcp
+ ttest_ssockt_cgenf_small_tcp4/1,
+ ttest_ssockt_cgenf_small_tcp6/1,
+ ttest_ssockt_cgenf_medium_tcp4/1,
+ ttest_ssockt_cgenf_medium_tcp6/1,
+ ttest_ssockt_cgenf_large_tcp4/1,
+ ttest_ssockt_cgenf_large_tcp6/1,
+
+ ttest_ssockt_cgeno_small_tcp4/1,
+ ttest_ssockt_cgeno_small_tcp6/1,
+ ttest_ssockt_cgeno_medium_tcp4/1,
+ ttest_ssockt_cgeno_medium_tcp6/1,
+ ttest_ssockt_cgeno_large_tcp4/1,
+ ttest_ssockt_cgeno_large_tcp6/1,
+
+ ttest_ssockt_cgent_small_tcp4/1,
+ ttest_ssockt_cgent_small_tcp6/1,
+ ttest_ssockt_cgent_medium_tcp4/1,
+ ttest_ssockt_cgent_medium_tcp6/1,
+ ttest_ssockt_cgent_large_tcp4/1,
+ ttest_ssockt_cgent_large_tcp6/1,
+
+ %% Server: transport = socket(tcp), active = true
+ %% Client: transport = socket(tcp)
+ ttest_ssockt_csockf_small_tcp4/1,
+ ttest_ssockt_csockf_small_tcp6/1,
+ ttest_ssockt_csockf_small_tcpL/1,
+ ttest_ssockt_csockf_medium_tcp4/1,
+ ttest_ssockt_csockf_medium_tcp6/1,
+ ttest_ssockt_csockf_medium_tcpL/1,
+ ttest_ssockt_csockf_large_tcp4/1,
+ ttest_ssockt_csockf_large_tcp6/1,
+ ttest_ssockt_csockf_large_tcpL/1,
+
+ ttest_ssockt_csocko_small_tcp4/1,
+ ttest_ssockt_csocko_small_tcp6/1,
+ ttest_ssockt_csocko_small_tcpL/1,
+ ttest_ssockt_csocko_medium_tcp4/1,
+ ttest_ssockt_csocko_medium_tcp6/1,
+ ttest_ssockt_csocko_medium_tcpL/1,
+ ttest_ssockt_csocko_large_tcp4/1,
+ ttest_ssockt_csocko_large_tcp6/1,
+ ttest_ssockt_csocko_large_tcpL/1,
+
+ ttest_ssockt_csockt_small_tcp4/1,
+ ttest_ssockt_csockt_small_tcp6/1,
+ ttest_ssockt_csockt_small_tcpL/1,
+ ttest_ssockt_csockt_medium_tcp4/1,
+ ttest_ssockt_csockt_medium_tcp6/1,
+ ttest_ssockt_csockt_medium_tcpL/1,
+ ttest_ssockt_csockt_large_tcp4/1,
+ ttest_ssockt_csockt_large_tcp6/1,
+ ttest_ssockt_csockt_large_tcpL/1,
+
+ %% Tickets
+ otp16359_maccept_tcp4/1,
+ otp16359_maccept_tcp6/1,
+ otp16359_maccept_tcpL/1
+ ]).
+
+
+%% Internal exports
+%% -export([]).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-define(LIB, socket_test_lib).
+-define(TTEST_LIB, socket_test_ttest_lib).
+-define(LOGGER, socket_test_logger).
+
+-define(BASIC_REQ, <<"hejsan">>).
+-define(BASIC_REP, <<"hoppsan">>).
+
+-define(DATA, <<"HOPPSAN">>). % Temporary
+-define(FAIL(R), exit(R)).
+
+-define(SLEEP(T), receive after T -> ok end).
+
+-define(MINS(M), timer:minutes(M)).
+-define(SECS(S), timer:seconds(S)).
+
+-define(TT(T), ct:timetrap(T)).
+
+-define(F(F, A), ?LIB:f(F, A)).
+
+
+-define(TPP_SMALL, lists:seq(1, 8)).
+-define(TPP_MEDIUM, lists:flatten(lists:duplicate(1024, ?TPP_SMALL))).
+-define(TPP_LARGE, lists:flatten(lists:duplicate(1024, ?TPP_MEDIUM))).
+
+-define(TPP_SMALL_NUM, 5000).
+-define(TPP_MEDIUM_NUM, 500).
+-define(TPP_LARGE_NUM, 50).
+-define(TPP_NUM(Config, Base), Base div lookup(esock_factor, 1, Config)).
+
+-define(TTEST_RUNTIME, ?SECS(10)).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+suite() ->
+ [{ct_hooks,[ts_install_cth]},
+ {timetrap,{minutes,1}}].
+
+all() ->
+ Groups = [{api, "ESOCK_TEST_API", include},
+ {socket_close, "ESOCK_TEST_SOCK_CLOSE", include},
+ {traffic, "ESOCK_TEST_TRAFFIC", include},
+ {ttest, "ESOCK_TEST_TTEST", exclude},
+ {tickets, "ESOCK_TEST_TICKETS", include}],
+ [use_group(Group, Env, Default) || {Group, Env, Default} <- Groups].
+
+use_group(Group, Env, Default) ->
+ case os:getenv(Env) of
+ false when (Default =:= include) ->
+ [{group, Group}];
+ false ->
+ [];
+ Val ->
+ case list_to_atom(string:to_lower(Val)) of
+ Use when (Use =:= include) orelse
+ (Use =:= enable) orelse
+ (Use =:= true) ->
+ [{group, Group}];
+ _ ->
+ []
+ end
+ end.
+
+
+groups() ->
+ [{api, [], api_cases()},
+ {api_misc, [], api_misc_cases()},
+ {api_basic, [], api_basic_cases()},
+ {api_from_fd, [], api_from_fd_cases()},
+ {api_async, [], api_async_cases()},
+ {api_options, [], api_options_cases()},
+ {api_options_otp, [], api_options_otp_cases()},
+ {api_options_socket, [], api_options_socket_cases()},
+ {api_option_sock_acceptconn, [], api_option_sock_acceptconn_cases()},
+ {api_option_sock_passcred, [], api_option_sock_passcred_cases()},
+ {api_option_sock_priority, [], api_option_sock_priority_cases()},
+ {api_option_sock_buf, [], api_option_sock_buf_cases()},
+ {api_option_sock_lowat, [], api_option_sock_lowat_cases()},
+ {api_option_sock_timeo, [], api_option_sock_timeo_cases()},
+ {api_option_sock_timestamp, [], api_option_sock_timestamp_cases()},
+ {api_options_ip, [], api_options_ip_cases()},
+ {api_options_ipv6, [], api_options_ipv6_cases()},
+ {api_options_tcp, [], api_options_tcp_cases()},
+ {api_options_udp, [], api_options_udp_cases()},
+ %% {api_options_sctp, [], api_options_sctp_cases()},
+ {api_op_with_timeout, [], api_op_with_timeout_cases()},
+ {reg, [], reg_simple_cases()},
+ {socket_close, [], socket_close_cases()},
+ {sc_ctrl_proc_exit, [], sc_cp_exit_cases()},
+ {sc_local_close, [], sc_lc_cases()},
+ {sc_remote_close, [], sc_rc_cases()},
+ {sc_remote_shutdown, [], sc_rs_cases()},
+ {traffic, [], traffic_cases()},
+ {traffic_counters, [], traffic_counters_cases()},
+ {traffic_chunks, [], traffic_chunks_cases()},
+ {traffic_ping_pong, [], traffic_ping_pong_cases()},
+ {traffic_pp_send_recv, [], traffic_pp_send_recv_cases()},
+ {traffic_pp_sendto_recvfrom, [], traffic_pp_sendto_recvfrom_cases()},
+ {traffic_pp_sendmsg_recvmsg, [], traffic_pp_sendmsg_recvmsg_cases()},
+ {ttest, [], ttest_cases()},
+ {ttest_sgenf, [], ttest_sgenf_cases()},
+ {ttest_sgenf_cgen, [], ttest_sgenf_cgen_cases()},
+ {ttest_sgenf_cgenf, [], ttest_sgenf_cgenf_cases()},
+ {ttest_sgenf_cgeno, [], ttest_sgenf_cgeno_cases()},
+ {ttest_sgenf_cgent, [], ttest_sgenf_cgent_cases()},
+ {ttest_sgenf_csock, [], ttest_sgenf_csock_cases()},
+ {ttest_sgenf_csockf, [], ttest_sgenf_csockf_cases()},
+ {ttest_sgenf_csocko, [], ttest_sgenf_csocko_cases()},
+ {ttest_sgenf_csockt, [], ttest_sgenf_csockt_cases()},
+ {ttest_sgeno, [], ttest_sgeno_cases()},
+ {ttest_sgeno_cgen, [], ttest_sgeno_cgen_cases()},
+ {ttest_sgeno_cgenf, [], ttest_sgeno_cgenf_cases()},
+ {ttest_sgeno_cgeno, [], ttest_sgeno_cgeno_cases()},
+ {ttest_sgeno_cgent, [], ttest_sgeno_cgent_cases()},
+ {ttest_sgeno_csock, [], ttest_sgeno_csock_cases()},
+ {ttest_sgeno_csockf, [], ttest_sgeno_csockf_cases()},
+ {ttest_sgeno_csocko, [], ttest_sgeno_csocko_cases()},
+ {ttest_sgeno_csockt, [], ttest_sgeno_csockt_cases()},
+ {ttest_sgent, [], ttest_sgent_cases()},
+ {ttest_sgent_cgen, [], ttest_sgent_cgen_cases()},
+ {ttest_sgent_cgenf, [], ttest_sgent_cgenf_cases()},
+ {ttest_sgent_cgeno, [], ttest_sgent_cgeno_cases()},
+ {ttest_sgent_cgent, [], ttest_sgent_cgent_cases()},
+ {ttest_sgent_csock, [], ttest_sgent_csock_cases()},
+ {ttest_sgent_csockf, [], ttest_sgent_csockf_cases()},
+ {ttest_sgent_csocko, [], ttest_sgent_csocko_cases()},
+ {ttest_sgent_csockt, [], ttest_sgent_csockt_cases()},
+ {ttest_ssockf, [], ttest_ssockf_cases()},
+ {ttest_ssockf_cgen, [], ttest_ssockf_cgen_cases()},
+ {ttest_ssockf_cgenf, [], ttest_ssockf_cgenf_cases()},
+ {ttest_ssockf_cgeno, [], ttest_ssockf_cgeno_cases()},
+ {ttest_ssockf_cgent, [], ttest_ssockf_cgent_cases()},
+ {ttest_ssockf_csock, [], ttest_ssockf_csock_cases()},
+ {ttest_ssockf_csockf, [], ttest_ssockf_csockf_cases()},
+ {ttest_ssockf_csocko, [], ttest_ssockf_csocko_cases()},
+ {ttest_ssockf_csockt, [], ttest_ssockf_csockt_cases()},
+ {ttest_ssocko, [], ttest_ssocko_cases()},
+ {ttest_ssocko_cgen, [], ttest_ssocko_cgen_cases()},
+ {ttest_ssocko_cgenf, [], ttest_ssocko_cgenf_cases()},
+ {ttest_ssocko_cgeno, [], ttest_ssocko_cgeno_cases()},
+ {ttest_ssocko_cgent, [], ttest_ssocko_cgent_cases()},
+ {ttest_ssocko_csock, [], ttest_ssocko_csock_cases()},
+ {ttest_ssocko_csockf, [], ttest_ssocko_csockf_cases()},
+ {ttest_ssocko_csocko, [], ttest_ssocko_csocko_cases()},
+ {ttest_ssocko_csockt, [], ttest_ssocko_csockt_cases()},
+ {ttest_ssockt, [], ttest_ssockt_cases()},
+ {ttest_ssockt_cgen, [], ttest_ssockt_cgen_cases()},
+ {ttest_ssockt_cgenf, [], ttest_ssockt_cgenf_cases()},
+ {ttest_ssockt_cgeno, [], ttest_ssockt_cgeno_cases()},
+ {ttest_ssockt_cgent, [], ttest_ssockt_cgent_cases()},
+ {ttest_ssockt_csock, [], ttest_ssockt_csock_cases()},
+ {ttest_ssockt_csockf, [], ttest_ssockt_csockf_cases()},
+ {ttest_ssockt_csocko, [], ttest_ssockt_csocko_cases()},
+ {ttest_ssockt_csockt, [], ttest_ssockt_csockt_cases()},
+
+ %% Ticket groups
+ {tickets, [], tickets_cases()},
+ {otp16359, [], otp16359_cases()}
+ ].
+
+api_cases() ->
+ [
+ {group, api_misc},
+ {group, api_basic},
+ {group, api_async},
+ {group, api_options},
+ {group, api_op_with_timeout}
+ ].
+
+api_misc_cases() ->
+ [
+ api_m_info,
+ api_m_debug
+ ].
+
+api_basic_cases() ->
+ [
+ api_b_open_and_info_udp4,
+ api_b_open_and_info_udp6,
+ api_b_open_and_info_tcp4,
+ api_b_open_and_info_tcp6,
+ api_b_open_and_close_udp4,
+ api_b_open_and_close_udp6,
+ api_b_open_and_close_tcp4,
+ api_b_open_and_close_tcp6,
+ api_b_open_and_close_udpL,
+ api_b_open_and_close_tcpL,
+ api_b_open_and_close_seqpL,
+ api_b_open_and_close_sctp4,
+ api_b_open_and_maybe_close_raw,
+ api_b_sendto_and_recvfrom_udp4,
+ api_b_sendto_and_recvfrom_udpL,
+ api_b_sendmsg_and_recvmsg_udp4,
+ api_b_sendmsg_and_recvmsg_udpL,
+ api_b_send_and_recv_tcp4,
+ api_b_send_and_recv_tcpL,
+ api_b_send_and_recv_seqpL,
+ api_b_sendmsg_and_recvmsg_tcp4,
+ api_b_sendmsg_and_recvmsg_tcpL,
+ api_b_sendmsg_and_recvmsg_seqpL,
+ api_b_sendmsg_and_recvmsg_sctp4
+ ].
+
+api_from_fd_cases() ->
+ [
+ api_ffd_open_wod_and_info_udp4,
+ api_ffd_open_wod_and_info_udp6,
+ api_ffd_open_wod_and_info_tcp4,
+ api_ffd_open_wod_and_info_tcp6,
+ api_ffd_open_wd_and_info_udp4,
+ api_ffd_open_wd_and_info_udp6,
+ api_ffd_open_wd_and_info_tcp4,
+ api_ffd_open_wd_and_info_tcp6,
+ api_ffd_open_and_open_wod_and_send_udp4,
+ api_ffd_open_and_open_wod_and_send_udp6,
+ api_ffd_open_and_open_wd_and_send_udp4,
+ api_ffd_open_and_open_wd_and_send_udp6,
+ api_ffd_open_connect_and_open_wod_and_send_tcp4,
+ api_ffd_open_connect_and_open_wod_and_send_tcp6,
+ api_ffd_open_connect_and_open_wd_and_send_tcp4,
+ api_ffd_open_connect_and_open_wd_and_send_tcp6
+ ].
+
+api_async_cases() ->
+ [
+ api_a_connect_tcp4,
+ api_a_connect_tcp6,
+ api_a_sendto_and_recvfrom_udp4,
+ api_a_sendto_and_recvfrom_udp6,
+ api_a_sendmsg_and_recvmsg_udp4,
+ api_a_sendmsg_and_recvmsg_udp6,
+ api_a_send_and_recv_tcp4,
+ api_a_send_and_recv_tcp6,
+ api_a_sendmsg_and_recvmsg_tcp4,
+ api_a_sendmsg_and_recvmsg_tcp6,
+ api_a_recvfrom_cancel_udp4,
+ api_a_recvfrom_cancel_udp6,
+ api_a_recvmsg_cancel_udp4,
+ api_a_recvmsg_cancel_udp6,
+ api_a_accept_cancel_tcp4,
+ api_a_accept_cancel_tcp6,
+ api_a_recv_cancel_tcp4,
+ api_a_recv_cancel_tcp6,
+ api_a_recvmsg_cancel_tcp4,
+ api_a_recvmsg_cancel_tcp6,
+ api_a_mrecvfrom_cancel_udp4,
+ api_a_mrecvfrom_cancel_udp6,
+ api_a_mrecvmsg_cancel_udp4,
+ api_a_mrecvmsg_cancel_udp6,
+ api_a_maccept_cancel_tcp4,
+ api_a_maccept_cancel_tcp6,
+ api_a_mrecv_cancel_tcp4,
+ api_a_mrecv_cancel_tcp6,
+ api_a_mrecvmsg_cancel_tcp4,
+ api_a_mrecvmsg_cancel_tcp6
+ ].
+
+api_options_cases() ->
+ [
+ {group, api_options_otp},
+ {group, api_options_socket},
+ {group, api_options_ip},
+ {group, api_options_ipv6},
+ {group, api_options_tcp},
+ {group, api_options_udp}
+ %% {group, api_options_sctp}
+ ].
+
+api_options_otp_cases() ->
+ [
+ api_opt_simple_otp_options,
+ api_opt_simple_otp_meta_option,
+ api_opt_simple_otp_rcvbuf_option,
+ api_opt_simple_otp_controlling_process
+ ].
+
+api_options_socket_cases() ->
+ [
+ {group, api_option_sock_acceptconn},
+ api_opt_sock_acceptfilter,
+ api_opt_sock_bindtodevice,
+ api_opt_sock_broadcast,
+ api_opt_sock_debug,
+ api_opt_sock_domain,
+ api_opt_sock_dontroute,
+ api_opt_sock_error,
+ api_opt_sock_keepalive,
+ api_opt_sock_linger,
+ api_opt_sock_mark,
+ api_opt_sock_oobinline,
+ {group, api_option_sock_passcred},
+ api_opt_sock_peek_off_tcpL,
+ api_opt_sock_peercred_tcpL,
+ {group, api_option_sock_priority},
+ {group, api_option_sock_buf},
+ {group, api_option_sock_lowat},
+ {group, api_option_sock_timeo},
+ {group, api_option_sock_timestamp}
+ ].
+
+api_option_sock_acceptconn_cases() ->
+ [
+ api_opt_sock_acceptconn_udp,
+ api_opt_sock_acceptconn_tcp
+ ].
+
+api_option_sock_passcred_cases() ->
+ [
+ %% api_opt_sock_passcred_udp4,
+ api_opt_sock_passcred_tcp4
+ ].
+
+api_option_sock_priority_cases() ->
+ [
+ api_opt_sock_priority_udp4,
+ api_opt_sock_priority_tcp4%,
+ %% api_opt_sock_priority_udp6,
+ %% api_opt_sock_priority_tcp6
+ ].
+
+api_option_sock_buf_cases() ->
+ [
+ api_opt_sock_rcvbuf_udp4,
+ api_opt_sock_sndbuf_udp4
+ ].
+
+api_option_sock_lowat_cases() ->
+ [
+ api_opt_sock_rcvlowat_udp4,
+ api_opt_sock_sndlowat_udp4
+ ].
+
+api_option_sock_timeo_cases() ->
+ [
+ api_opt_sock_rcvtimeo_udp4,
+ api_opt_sock_sndtimeo_udp4
+ ].
+
+api_option_sock_timestamp_cases() ->
+ [
+ api_opt_sock_timestamp_udp4,
+ api_opt_sock_timestamp_tcp4
+ ].
+
+api_options_ip_cases() ->
+ [
+ api_opt_ip_add_drop_membership,
+ api_opt_ip_pktinfo_udp4,
+ api_opt_ip_recvopts_udp4,
+ api_opt_ip_recvorigdstaddr_udp4,
+ api_opt_ip_recvtos_udp4,
+ api_opt_ip_recvttl_udp4,
+ api_opt_ip_tos_udp4,
+ api_opt_ip_recverr_udp4,
+
+ %% Should be last!
+ api_opt_ip_mopts_udp4
+ ].
+
+api_options_ipv6_cases() ->
+ [
+ api_opt_ipv6_recvpktinfo_udp6,
+ api_opt_ipv6_flowinfo_udp6,
+ api_opt_ipv6_hoplimit_udp6,
+ api_opt_ipv6_tclass_udp6,
+ api_opt_ipv6_recverr_udp6,
+
+ %% Should be last!
+ api_opt_ipv6_mopts_udp6
+ ].
+
+api_options_tcp_cases() ->
+ [
+ api_opt_tcp_congestion_tcp4,
+ %% api_opt_tcp_congestion_tcp6,
+ api_opt_tcp_cork_tcp4,
+ %% api_opt_tcp_cork_tcp6,
+ api_opt_tcp_maxseg_tcp4,
+ %% api_opt_tcp_maxseg_tcp6,
+ api_opt_tcp_nodelay_tcp4%,
+ %% api_opt_tcp_nodelay_tcp6
+ ].
+
+api_options_udp_cases() ->
+ [
+ api_opt_udp_cork_udp4%,
+ %% api_opt_udp_cork_udp6
+ ].
+
+api_op_with_timeout_cases() ->
+ [
+ api_to_connect_tcp4,
+ api_to_connect_tcp6,
+ api_to_accept_tcp4,
+ api_to_accept_tcp6,
+ api_to_maccept_tcp4,
+ api_to_maccept_tcp6,
+ api_to_send_tcp4,
+ api_to_send_tcp6,
+ api_to_sendto_udp4,
+ api_to_sendto_udp6,
+ api_to_sendmsg_tcp4,
+ api_to_sendmsg_tcp6,
+ api_to_recv_udp4,
+ api_to_recv_udp6,
+ api_to_recv_tcp4,
+ api_to_recv_tcp6,
+ api_to_recvfrom_udp4,
+ api_to_recvfrom_udp6,
+ api_to_recvmsg_udp4,
+ api_to_recvmsg_udp6,
+ api_to_recvmsg_tcp4,
+ api_to_recvmsg_tcp6
+ ].
+
+%% Socket Registry "simple" test cases
+reg_simple_cases() ->
+ [
+ reg_s_single_open_and_close_and_count
+ ].
+
+
+%% These cases tests what happens when the socket is closed/shutdown,
+%% locally or remotely.
+socket_close_cases() ->
+ [
+ {group, sc_ctrl_proc_exit},
+ {group, sc_local_close},
+ {group, sc_remote_close},
+ {group, sc_remote_shutdown}
+ ].
+
+%% These cases are all about socket cleanup after the controlling process
+%% exits *without* explicitly calling socket:close/1.
+sc_cp_exit_cases() ->
+ [
+ sc_cpe_socket_cleanup_tcp4,
+ sc_cpe_socket_cleanup_tcp6,
+ sc_cpe_socket_cleanup_tcpL,
+ sc_cpe_socket_cleanup_udp4,
+ sc_cpe_socket_cleanup_udp6,
+ sc_cpe_socket_cleanup_udpL
+ ].
+
+%% These cases tests what happens when the socket is closed locally.
+sc_lc_cases() ->
+ [
+ sc_lc_recv_response_tcp4,
+ sc_lc_recv_response_tcp6,
+ sc_lc_recv_response_tcpL,
+
+ sc_lc_recvfrom_response_udp4,
+ sc_lc_recvfrom_response_udp6,
+ sc_lc_recvfrom_response_udpL,
+
+ sc_lc_recvmsg_response_tcp4,
+ sc_lc_recvmsg_response_tcp6,
+ sc_lc_recvmsg_response_tcpL,
+ sc_lc_recvmsg_response_udp4,
+ sc_lc_recvmsg_response_udp6,
+ sc_lc_recvmsg_response_udpL,
+
+ sc_lc_acceptor_response_tcp4,
+ sc_lc_acceptor_response_tcp6,
+ sc_lc_acceptor_response_tcpL
+ ].
+
+%% These cases tests what happens when the socket is closed remotely.
+sc_rc_cases() ->
+ [
+ sc_rc_recv_response_tcp4,
+ sc_rc_recv_response_tcp6,
+ sc_rc_recv_response_tcpL,
+
+ sc_rc_recvmsg_response_tcp4,
+ sc_rc_recvmsg_response_tcp6,
+ sc_rc_recvmsg_response_tcpL
+ ].
+
+%% These cases tests what happens when the socket is shutdown/closed remotely
+%% after writing and reading is ongoing.
+sc_rs_cases() ->
+ [
+ sc_rs_recv_send_shutdown_receive_tcp4,
+ sc_rs_recv_send_shutdown_receive_tcp6,
+ sc_rs_recv_send_shutdown_receive_tcpL,
+
+ sc_rs_recvmsg_send_shutdown_receive_tcp4,
+ sc_rs_recvmsg_send_shutdown_receive_tcp6,
+ sc_rs_recvmsg_send_shutdown_receive_tcpL
+ ].
+
+
+traffic_cases() ->
+ [
+ {group, traffic_counters},
+ {group, traffic_chunks},
+ {group, traffic_ping_pong}
+ ].
+
+traffic_counters_cases() ->
+ [
+ traffic_send_and_recv_counters_tcp4,
+ traffic_send_and_recv_counters_tcp6,
+ traffic_send_and_recv_counters_tcpL,
+ traffic_sendmsg_and_recvmsg_counters_tcp4,
+ traffic_sendmsg_and_recvmsg_counters_tcp6,
+ traffic_sendmsg_and_recvmsg_counters_tcpL,
+ traffic_sendto_and_recvfrom_counters_udp4,
+ traffic_sendto_and_recvfrom_counters_udp6,
+ traffic_sendto_and_recvfrom_counters_udpL,
+ traffic_sendmsg_and_recvmsg_counters_udp4,
+ traffic_sendmsg_and_recvmsg_counters_udp6,
+ traffic_sendmsg_and_recvmsg_counters_udpL
+ ].
+
+traffic_chunks_cases() ->
+ [
+ traffic_send_and_recv_chunks_tcp4,
+ traffic_send_and_recv_chunks_tcp6,
+ traffic_send_and_recv_chunks_tcpL
+ ].
+
+traffic_ping_pong_cases() ->
+ [
+ {group, traffic_pp_send_recv},
+ {group, traffic_pp_sendto_recvfrom},
+ {group, traffic_pp_sendmsg_recvmsg}
+ ].
+
+traffic_pp_send_recv_cases() ->
+ [
+ traffic_ping_pong_small_send_and_recv_tcp4,
+ traffic_ping_pong_small_send_and_recv_tcp6,
+ traffic_ping_pong_small_send_and_recv_tcpL,
+ traffic_ping_pong_medium_send_and_recv_tcp4,
+ traffic_ping_pong_medium_send_and_recv_tcp6,
+ traffic_ping_pong_medium_send_and_recv_tcpL,
+ traffic_ping_pong_large_send_and_recv_tcp4,
+ traffic_ping_pong_large_send_and_recv_tcp6,
+ traffic_ping_pong_large_send_and_recv_tcpL
+ ].
+
+traffic_pp_sendto_recvfrom_cases() ->
+ [
+ traffic_ping_pong_small_sendto_and_recvfrom_udp4,
+ traffic_ping_pong_small_sendto_and_recvfrom_udp6,
+ traffic_ping_pong_small_sendto_and_recvfrom_udpL,
+ traffic_ping_pong_medium_sendto_and_recvfrom_udp4,
+ traffic_ping_pong_medium_sendto_and_recvfrom_udp6,
+ traffic_ping_pong_medium_sendto_and_recvfrom_udpL
+ ].
+
+traffic_pp_sendmsg_recvmsg_cases() ->
+ [
+ traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4,
+ traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6,
+ traffic_ping_pong_small_sendmsg_and_recvmsg_tcpL,
+ traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4,
+ traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6,
+ traffic_ping_pong_medium_sendmsg_and_recvmsg_tcpL,
+ traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4,
+ traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6,
+ traffic_ping_pong_large_sendmsg_and_recvmsg_tcpL,
+
+ traffic_ping_pong_small_sendmsg_and_recvmsg_udp4,
+ traffic_ping_pong_small_sendmsg_and_recvmsg_udp6,
+ traffic_ping_pong_small_sendmsg_and_recvmsg_udpL,
+ traffic_ping_pong_medium_sendmsg_and_recvmsg_udp4,
+ traffic_ping_pong_medium_sendmsg_and_recvmsg_udp6,
+ traffic_ping_pong_medium_sendmsg_and_recvmsg_udpL
+ ].
+
+ttest_cases() ->
+ [
+ %% Server: transport = gen_tcp, active = false
+ {group, ttest_sgenf},
+
+ %% Server: transport = gen_tcp, active = once
+ {group, ttest_sgeno},
+
+ %% Server: transport = gen_tcp, active = true
+ {group, ttest_sgent},
+
+ %% Server: transport = socket(tcp), active = false
+ {group, ttest_ssockf},
+
+ %% Server: transport = socket(tcp), active = once
+ {group, ttest_ssocko},
+
+ %% Server: transport = socket(tcp), active = true
+ {group, ttest_ssockt}
+
+ ].
+
+
+%% Server: transport = gen_tcp, active = false
+ttest_sgenf_cases() ->
+ [
+ {group, ttest_sgenf_cgen},
+ {group, ttest_sgenf_csock}
+ ].
+
+%% Server: transport = gen_tcp, active = false
+%% Client: transport = gen_tcp
+ttest_sgenf_cgen_cases() ->
+ [
+ {group, ttest_sgenf_cgenf},
+ {group, ttest_sgenf_cgeno},
+ {group, ttest_sgenf_cgent}
+ ].
+
+%% Server: transport = gen_tcp, active = false
+%% Client: transport = gen_tcp, active = false
+ttest_sgenf_cgenf_cases() ->
+ [
+ ttest_sgenf_cgenf_small_tcp4,
+ ttest_sgenf_cgenf_small_tcp6,
+
+ ttest_sgenf_cgenf_medium_tcp4,
+ ttest_sgenf_cgenf_medium_tcp6,
+
+ ttest_sgenf_cgenf_large_tcp4,
+ ttest_sgenf_cgenf_large_tcp6
+ ].
+
+%% Server: transport = gen_tcp, active = false
+%% Client: transport = gen_tcp, active = once
+ttest_sgenf_cgeno_cases() ->
+ [
+ ttest_sgenf_cgeno_small_tcp4,
+ ttest_sgenf_cgeno_small_tcp6,
+
+ ttest_sgenf_cgeno_medium_tcp4,
+ ttest_sgenf_cgeno_medium_tcp6,
+
+ ttest_sgenf_cgeno_large_tcp4,
+ ttest_sgenf_cgeno_large_tcp6
+ ].
+
+%% Server: transport = gen_tcp, active = false
+%% Client: transport = gen_tcp, active = true
+ttest_sgenf_cgent_cases() ->
+ [
+ ttest_sgenf_cgent_small_tcp4,
+ ttest_sgenf_cgent_small_tcp6,
+
+ ttest_sgenf_cgent_medium_tcp4,
+ ttest_sgenf_cgent_medium_tcp6,
+
+ ttest_sgenf_cgent_large_tcp4,
+ ttest_sgenf_cgent_large_tcp6
+ ].
+
+%% Server: transport = gen_tcp, active = false
+%% Client: transport = socket(tcp)
+ttest_sgenf_csock_cases() ->
+ [
+ {group, ttest_sgenf_csockf},
+ {group, ttest_sgenf_csocko},
+ {group, ttest_sgenf_csockt}
+ ].
+
+ttest_sgenf_csockf_cases() ->
+ [
+ ttest_sgenf_csockf_small_tcp4,
+ ttest_sgenf_csockf_small_tcp6,
+
+ ttest_sgenf_csockf_medium_tcp4,
+ ttest_sgenf_csockf_medium_tcp6,
+
+ ttest_sgenf_csockf_large_tcp4,
+ ttest_sgenf_csockf_large_tcp6
+ ].
+
+ttest_sgenf_csocko_cases() ->
+ [
+ ttest_sgenf_csocko_small_tcp4,
+ ttest_sgenf_csocko_small_tcp6,
+
+ ttest_sgenf_csocko_medium_tcp4,
+ ttest_sgenf_csocko_medium_tcp6,
+
+ ttest_sgenf_csocko_large_tcp4,
+ ttest_sgenf_csocko_large_tcp6
+ ].
+
+ttest_sgenf_csockt_cases() ->
+ [
+ ttest_sgenf_csockt_small_tcp4,
+ ttest_sgenf_csockt_small_tcp6,
+
+ ttest_sgenf_csockt_medium_tcp4,
+ ttest_sgenf_csockt_medium_tcp6,
+
+ ttest_sgenf_csockt_large_tcp4,
+ ttest_sgenf_csockt_large_tcp6
+ ].
+
+%% Server: transport = gen_tcp, active = once
+ttest_sgeno_cases() ->
+ [
+ {group, ttest_sgeno_cgen},
+ {group, ttest_sgeno_csock}
+ ].
+
+%% Server: transport = gen_tcp, active = once
+%% Client: transport = gen_tcp
+ttest_sgeno_cgen_cases() ->
+ [
+ {group, ttest_sgeno_cgenf},
+ {group, ttest_sgeno_cgeno},
+ {group, ttest_sgeno_cgent}
+ ].
+
+%% Server: transport = gen_tcp, active = once
+%% Client: transport = gen_tcp, active = false
+ttest_sgeno_cgenf_cases() ->
+ [
+ ttest_sgeno_cgenf_small_tcp4,
+ ttest_sgeno_cgenf_small_tcp6,
+
+ ttest_sgeno_cgenf_medium_tcp4,
+ ttest_sgeno_cgenf_medium_tcp6,
+
+ ttest_sgeno_cgenf_large_tcp4,
+ ttest_sgeno_cgenf_large_tcp6
+ ].
+
+%% Server: transport = gen_tcp, active = once
+%% Client: transport = gen_tcp, active = once
+ttest_sgeno_cgeno_cases() ->
+ [
+ ttest_sgeno_cgeno_small_tcp4,
+ ttest_sgeno_cgeno_small_tcp6,
+
+ ttest_sgeno_cgeno_medium_tcp4,
+ ttest_sgeno_cgeno_medium_tcp6,
+
+ ttest_sgeno_cgeno_large_tcp4,
+ ttest_sgeno_cgeno_large_tcp6
+ ].
+
+%% Server: transport = gen_tcp, active = once
+%% Client: transport = gen_tcp, active = true
+ttest_sgeno_cgent_cases() ->
+ [
+ ttest_sgeno_cgent_small_tcp4,
+ ttest_sgeno_cgent_small_tcp6,
+
+ ttest_sgeno_cgent_medium_tcp4,
+ ttest_sgeno_cgent_medium_tcp6,
+
+ ttest_sgeno_cgent_large_tcp4,
+ ttest_sgeno_cgent_large_tcp6
+ ].
+
+%% Server: transport = gen_tcp, active = once
+%% Client: transport = socket(tcp)
+ttest_sgeno_csock_cases() ->
+ [
+ {group, ttest_sgeno_csockf},
+ {group, ttest_sgeno_csocko},
+ {group, ttest_sgeno_csockt}
+ ].
+
+ttest_sgeno_csockf_cases() ->
+ [
+ ttest_sgeno_csockf_small_tcp4,
+ ttest_sgeno_csockf_small_tcp6,
+
+ ttest_sgeno_csockf_medium_tcp4,
+ ttest_sgeno_csockf_medium_tcp6,
+
+ ttest_sgeno_csockf_large_tcp4,
+ ttest_sgeno_csockf_large_tcp6
+ ].
+
+ttest_sgeno_csocko_cases() ->
+ [
+ ttest_sgeno_csocko_small_tcp4,
+ ttest_sgeno_csocko_small_tcp6,
+
+ ttest_sgeno_csocko_medium_tcp4,
+ ttest_sgeno_csocko_medium_tcp6,
+
+ ttest_sgeno_csocko_large_tcp4,
+ ttest_sgeno_csocko_large_tcp6
+ ].
+
+ttest_sgeno_csockt_cases() ->
+ [
+ ttest_sgeno_csockt_small_tcp4,
+ ttest_sgeno_csockt_small_tcp6,
+
+ ttest_sgeno_csockt_medium_tcp4,
+ ttest_sgeno_csockt_medium_tcp6,
+
+ ttest_sgeno_csockt_large_tcp4,
+ ttest_sgeno_csockt_large_tcp6
+ ].
+
+%% Server: transport = gen_tcp, active = true
+ttest_sgent_cases() ->
+ [
+ {group, ttest_sgent_cgen},
+ {group, ttest_sgent_csock}
+ ].
+
+%% Server: transport = gen_tcp, active = true
+%% Client: transport = gen_tcp
+ttest_sgent_cgen_cases() ->
+ [
+ {group, ttest_sgent_cgenf},
+ {group, ttest_sgent_cgeno},
+ {group, ttest_sgent_cgent}
+ ].
+
+%% Server: transport = gen_tcp, active = true
+%% Client: transport = gen_tcp, active = false
+ttest_sgent_cgenf_cases() ->
+ [
+ ttest_sgent_cgenf_small_tcp4,
+ ttest_sgent_cgenf_small_tcp6,
+
+ ttest_sgent_cgenf_medium_tcp4,
+ ttest_sgent_cgenf_medium_tcp6,
+
+ ttest_sgent_cgenf_large_tcp4,
+ ttest_sgent_cgenf_large_tcp6
+ ].
+
+%% Server: transport = gen_tcp, active = true
+%% Client: transport = gen_tcp, active = once
+ttest_sgent_cgeno_cases() ->
+ [
+ ttest_sgent_cgeno_small_tcp4,
+ ttest_sgent_cgeno_small_tcp6,
+
+ ttest_sgent_cgeno_medium_tcp4,
+ ttest_sgent_cgeno_medium_tcp6,
+
+ ttest_sgent_cgeno_large_tcp4,
+ ttest_sgent_cgeno_large_tcp6
+ ].
+
+%% Server: transport = gen_tcp, active = true
+%% Client: transport = gen_tcp, active = true
+ttest_sgent_cgent_cases() ->
+ [
+ ttest_sgent_cgent_small_tcp4,
+ ttest_sgent_cgent_small_tcp6,
+
+ ttest_sgent_cgent_medium_tcp4,
+ ttest_sgent_cgent_medium_tcp6,
+
+ ttest_sgent_cgent_large_tcp4,
+ ttest_sgent_cgent_large_tcp6
+ ].
+
+%% Server: transport = gen_tcp, active = true
+%% Client: transport = socket(tcp)
+ttest_sgent_csock_cases() ->
+ [
+ {group, ttest_sgent_csockf},
+ {group, ttest_sgent_csocko},
+ {group, ttest_sgent_csockt}
+ ].
+
+ttest_sgent_csockf_cases() ->
+ [
+ ttest_sgent_csockf_small_tcp4,
+ ttest_sgent_csockf_small_tcp6,
+
+ ttest_sgent_csockf_medium_tcp4,
+ ttest_sgent_csockf_medium_tcp6,
+
+ ttest_sgent_csockf_large_tcp4,
+ ttest_sgent_csockf_large_tcp6
+ ].
+
+ttest_sgent_csocko_cases() ->
+ [
+ ttest_sgent_csocko_small_tcp4,
+ ttest_sgent_csocko_small_tcp6,
+
+ ttest_sgent_csocko_medium_tcp4,
+ ttest_sgent_csocko_medium_tcp6,
+
+ ttest_sgent_csocko_large_tcp4,
+ ttest_sgent_csocko_large_tcp6
+ ].
+
+ttest_sgent_csockt_cases() ->
+ [
+ ttest_sgent_csockt_small_tcp4,
+ ttest_sgent_csockt_small_tcp6,
+
+ ttest_sgent_csockt_medium_tcp4,
+ ttest_sgent_csockt_medium_tcp6,
+
+ ttest_sgent_csockt_large_tcp4,
+ ttest_sgent_csockt_large_tcp6
+ ].
+
+%% Server: transport = socket(tcp), active = false
+ttest_ssockf_cases() ->
+ [
+ {group, ttest_ssockf_cgen},
+ {group, ttest_ssockf_csock}
+ ].
+
+%% Server: transport = socket(tcp), active = false
+%% Client: transport = gen_tcp
+ttest_ssockf_cgen_cases() ->
+ [
+ {group, ttest_ssockf_cgenf},
+ {group, ttest_ssockf_cgeno},
+ {group, ttest_ssockf_cgent}
+ ].
+
+%% Server: transport = socket(tcp), active = false
+%% Client: transport = gen_tcp, active = false
+ttest_ssockf_cgenf_cases() ->
+ [
+ ttest_ssockf_cgenf_small_tcp4,
+ ttest_ssockf_cgenf_small_tcp6,
+
+ ttest_ssockf_cgenf_medium_tcp4,
+ ttest_ssockf_cgenf_medium_tcp6,
+
+ ttest_ssockf_cgenf_large_tcp4,
+ ttest_ssockf_cgenf_large_tcp6
+ ].
+
+%% Server: transport = socket(tcp), active = false
+%% Client: transport = gen_tcp, active = once
+ttest_ssockf_cgeno_cases() ->
+ [
+ ttest_ssockf_cgeno_small_tcp4,
+ ttest_ssockf_cgeno_small_tcp6,
+
+ ttest_ssockf_cgeno_medium_tcp4,
+ ttest_ssockf_cgeno_medium_tcp6,
+
+ ttest_ssockf_cgeno_large_tcp4,
+ ttest_ssockf_cgeno_large_tcp6
+ ].
+
+%% Server: transport = socket(tcp), active = false
+%% Client: transport = gen_tcp, active = true
+ttest_ssockf_cgent_cases() ->
+ [
+ ttest_ssockf_cgent_small_tcp4,
+ ttest_ssockf_cgent_small_tcp6,
+
+ ttest_ssockf_cgent_medium_tcp4,
+ ttest_ssockf_cgent_medium_tcp6,
+
+ ttest_ssockf_cgent_large_tcp4,
+ ttest_ssockf_cgent_large_tcp6
+ ].
+
+%% Server: transport = socket(tcp), active = false
+%% Client: transport = socket(tcp)
+ttest_ssockf_csock_cases() ->
+ [
+ {group, ttest_ssockf_csockf},
+ {group, ttest_ssockf_csocko},
+ {group, ttest_ssockf_csockt}
+ ].
+
+%% Server: transport = socket(tcp), active = false
+%% Client: transport = socket(tcp), active = false
+ttest_ssockf_csockf_cases() ->
+ [
+ ttest_ssockf_csockf_small_tcp4,
+ ttest_ssockf_csockf_small_tcp6,
+ ttest_ssockf_csockf_small_tcpL,
+
+ ttest_ssockf_csockf_medium_tcp4,
+ ttest_ssockf_csockf_medium_tcp6,
+ ttest_ssockf_csockf_medium_tcpL,
+
+ ttest_ssockf_csockf_large_tcp4,
+ ttest_ssockf_csockf_large_tcp6,
+ ttest_ssockf_csockf_large_tcpL
+ ].
+
+%% Server: transport = socket(tcp), active = false
+%% Client: transport = socket(tcp), active = once
+ttest_ssockf_csocko_cases() ->
+ [
+ ttest_ssockf_csocko_small_tcp4,
+ ttest_ssockf_csocko_small_tcp6,
+ ttest_ssockf_csocko_small_tcpL,
+
+ ttest_ssockf_csocko_medium_tcp4,
+ ttest_ssockf_csocko_medium_tcp6,
+ ttest_ssockf_csocko_medium_tcpL,
+
+ ttest_ssockf_csocko_large_tcp4,
+ ttest_ssockf_csocko_large_tcp6,
+ ttest_ssockf_csocko_large_tcpL
+ ].
+
+%% Server: transport = socket(tcp), active = false
+%% Client: transport = socket(tcp), active = true
+ttest_ssockf_csockt_cases() ->
+ [
+ ttest_ssockf_csockt_small_tcp4,
+ ttest_ssockf_csockt_small_tcp6,
+ ttest_ssockf_csockt_small_tcpL,
+
+ ttest_ssockf_csockt_medium_tcp4,
+ ttest_ssockf_csockt_medium_tcp6,
+ ttest_ssockf_csockt_medium_tcpL,
+
+ ttest_ssockf_csockt_large_tcp4,
+ ttest_ssockf_csockt_large_tcp6,
+ ttest_ssockf_csockt_large_tcpL
+ ].
+
+%% Server: transport = socket(tcp), active = once
+ttest_ssocko_cases() ->
+ [
+ {group, ttest_ssocko_cgen},
+ {group, ttest_ssocko_csock}
+ ].
+
+%% Server: transport = socket(tcp), active = once
+%% Client: transport = gen_tcp
+ttest_ssocko_cgen_cases() ->
+ [
+ {group, ttest_ssocko_cgenf},
+ {group, ttest_ssocko_cgeno},
+ {group, ttest_ssocko_cgent}
+ ].
+
+%% Server: transport = socket(tcp), active = once
+%% Client: transport = gen_tcp, active = false
+ttest_ssocko_cgenf_cases() ->
+ [
+ ttest_ssocko_cgenf_small_tcp4,
+ ttest_ssocko_cgenf_small_tcp6,
+
+ ttest_ssocko_cgenf_medium_tcp4,
+ ttest_ssocko_cgenf_medium_tcp6,
+
+ ttest_ssocko_cgenf_large_tcp4,
+ ttest_ssocko_cgenf_large_tcp6
+ ].
+
+%% Server: transport = socket(tcp), active = once
+%% Client: transport = gen_tcp, active = once
+ttest_ssocko_cgeno_cases() ->
+ [
+ ttest_ssocko_cgeno_small_tcp4,
+ ttest_ssocko_cgeno_small_tcp6,
+
+ ttest_ssocko_cgeno_medium_tcp4,
+ ttest_ssocko_cgeno_medium_tcp6,
+
+ ttest_ssocko_cgeno_large_tcp4,
+ ttest_ssocko_cgeno_large_tcp6
+ ].
+
+%% Server: transport = socket(tcp), active = once
+%% Client: transport = gen_tcp, active = true
+ttest_ssocko_cgent_cases() ->
+ [
+ ttest_ssocko_cgent_small_tcp4,
+ ttest_ssocko_cgent_small_tcp6,
+
+ ttest_ssocko_cgent_medium_tcp4,
+ ttest_ssocko_cgent_medium_tcp6,
+
+ ttest_ssocko_cgent_large_tcp4,
+ ttest_ssocko_cgent_large_tcp6
+ ].
+
+%% Server: transport = socket(tcp), active = once
+%% Client: transport = socket(tcp)
+ttest_ssocko_csock_cases() ->
+ [
+ {group, ttest_ssocko_csockf},
+ {group, ttest_ssocko_csocko},
+ {group, ttest_ssocko_csockt}
+ ].
+
+%% Server: transport = socket(tcp), active = once
+%% Client: transport = socket(tcp), active = false
+ttest_ssocko_csockf_cases() ->
+ [
+ ttest_ssocko_csockf_small_tcp4,
+ ttest_ssocko_csockf_small_tcp6,
+ ttest_ssocko_csockf_small_tcpL,
+
+ ttest_ssocko_csockf_medium_tcp4,
+ ttest_ssocko_csockf_medium_tcp6,
+ ttest_ssocko_csockf_medium_tcpL,
+
+ ttest_ssocko_csockf_large_tcp4,
+ ttest_ssocko_csockf_large_tcp6,
+ ttest_ssocko_csockf_large_tcpL
+ ].
+
+%% Server: transport = socket(tcp), active = once
+%% Client: transport = socket(tcp), active = once
+ttest_ssocko_csocko_cases() ->
+ [
+ ttest_ssocko_csocko_small_tcp4,
+ ttest_ssocko_csocko_small_tcp6,
+ ttest_ssocko_csocko_small_tcpL,
+
+ ttest_ssocko_csocko_medium_tcp4,
+ ttest_ssocko_csocko_medium_tcp6,
+ ttest_ssocko_csocko_medium_tcpL,
+
+ ttest_ssocko_csocko_large_tcp4,
+ ttest_ssocko_csocko_large_tcp6,
+ ttest_ssocko_csocko_large_tcpL
+ ].
+
+%% Server: transport = socket(tcp), active = once
+%% Client: transport = socket(tcp), active = true
+ttest_ssocko_csockt_cases() ->
+ [
+ ttest_ssocko_csockt_small_tcp4,
+ ttest_ssocko_csockt_small_tcp6,
+ ttest_ssocko_csockt_small_tcpL,
+
+ ttest_ssocko_csockt_medium_tcp4,
+ ttest_ssocko_csockt_medium_tcp6,
+ ttest_ssocko_csockt_medium_tcpL,
+
+ ttest_ssocko_csockt_large_tcp4,
+ ttest_ssocko_csockt_large_tcp6,
+ ttest_ssocko_csockt_large_tcpL
+ ].
+
+%% Server: transport = socket(tcp), active = true
+ttest_ssockt_cases() ->
+ [
+ {group, ttest_ssockt_cgen},
+ {group, ttest_ssockt_csock}
+ ].
+
+%% Server: transport = socket(tcp), active = true
+%% Client: transport = gen_tcp
+ttest_ssockt_cgen_cases() ->
+ [
+ {group, ttest_ssockt_cgenf},
+ {group, ttest_ssockt_cgeno},
+ {group, ttest_ssockt_cgent}
+ ].
+
+%% Server: transport = socket(tcp), active = true
+%% Client: transport = gen_tcp, active = false
+ttest_ssockt_cgenf_cases() ->
+ [
+ ttest_ssockt_cgenf_small_tcp4,
+ ttest_ssockt_cgenf_small_tcp6,
+
+ ttest_ssockt_cgenf_medium_tcp4,
+ ttest_ssockt_cgenf_medium_tcp6,
+
+ ttest_ssockt_cgenf_large_tcp4,
+ ttest_ssockt_cgenf_large_tcp6
+ ].
+
+%% Server: transport = socket(tcp), active = true
+%% Client: transport = gen_tcp, active = once
+ttest_ssockt_cgeno_cases() ->
+ [
+ ttest_ssockt_cgeno_small_tcp4,
+ ttest_ssockt_cgeno_small_tcp6,
+
+ ttest_ssockt_cgeno_medium_tcp4,
+ ttest_ssockt_cgeno_medium_tcp6,
+
+ ttest_ssockt_cgeno_large_tcp4,
+ ttest_ssockt_cgeno_large_tcp6
+ ].
+
+%% Server: transport = socket(tcp), active = true
+%% Client: transport = gen_tcp, active = true
+ttest_ssockt_cgent_cases() ->
+ [
+ ttest_ssockt_cgent_small_tcp4,
+ ttest_ssockt_cgent_small_tcp6,
+
+ ttest_ssockt_cgent_medium_tcp4,
+ ttest_ssockt_cgent_medium_tcp6,
+
+ ttest_ssockt_cgent_large_tcp4,
+ ttest_ssockt_cgent_large_tcp6
+ ].
+
+%% Server: transport = socket(tcp), active = true
+%% Client: transport = socket(tcp)
+ttest_ssockt_csock_cases() ->
+ [
+ {group, ttest_ssockt_csockf},
+ {group, ttest_ssockt_csocko},
+ {group, ttest_ssockt_csockt}
+ ].
+
+%% Server: transport = socket(tcp), active = true
+%% Client: transport = socket(tcp), active = false
+ttest_ssockt_csockf_cases() ->
+ [
+ ttest_ssockt_csockf_small_tcp4,
+ ttest_ssockt_csockf_small_tcp6,
+ ttest_ssockt_csockf_small_tcpL,
+
+ ttest_ssockt_csockf_medium_tcp4,
+ ttest_ssockt_csockf_medium_tcp6,
+ ttest_ssockt_csockf_medium_tcpL,
+
+ ttest_ssockt_csockf_large_tcp4,
+ ttest_ssockt_csockf_large_tcp6,
+ ttest_ssockt_csockf_large_tcpL
+ ].
+
+%% Server: transport = socket(tcp), active = true
+%% Client: transport = socket(tcp), active = once
+ttest_ssockt_csocko_cases() ->
+ [
+ ttest_ssockt_csocko_small_tcp4,
+ ttest_ssockt_csocko_small_tcp6,
+ ttest_ssockt_csocko_small_tcpL,
+
+ ttest_ssockt_csocko_medium_tcp4,
+ ttest_ssockt_csocko_medium_tcp6,
+ ttest_ssockt_csocko_medium_tcpL,
+
+ ttest_ssockt_csocko_large_tcp4,
+ ttest_ssockt_csocko_large_tcp6,
+ ttest_ssockt_csocko_large_tcpL
+ ].
+
+%% Server: transport = socket(tcp), active = true
+%% Client: transport = socket(tcp), active = true
+ttest_ssockt_csockt_cases() ->
+ [
+ ttest_ssockt_csockt_small_tcp4,
+ ttest_ssockt_csockt_small_tcp6,
+ ttest_ssockt_csockt_small_tcpL,
+
+ ttest_ssockt_csockt_medium_tcp4,
+ ttest_ssockt_csockt_medium_tcp6,
+ ttest_ssockt_csockt_medium_tcpL,
+
+ ttest_ssockt_csockt_large_tcp4,
+ ttest_ssockt_csockt_large_tcp6,
+ ttest_ssockt_csockt_large_tcpL
+ ].
+
+tickets_cases() ->
+ [
+ {group, otp16359}
+ ].
+
+otp16359_cases() ->
+ [
+ otp16359_maccept_tcp4,
+ otp16359_maccept_tcp6,
+ otp16359_maccept_tcpL
+ ].
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+init_per_suite(Config) ->
+ ct:timetrap(?MINS(2)),
+ Factor = analyze_and_print_host_info(),
+ try socket:info() of
+ #{} ->
+ case quiet_mode(Config) of
+ default ->
+ ?LOGGER:start(),
+ [{esock_factor, Factor} | Config];
+ Quiet ->
+ ?LOGGER:start(Quiet),
+ [{esock_factor, Factor},
+ {esock_test_quiet, Quiet} | Config]
+ end
+ catch
+ error : notsup ->
+ {skip, "esock not supported"}
+ end.
+
+end_per_suite(_) ->
+ (catch ?LOGGER:stop()),
+ ok.
+
+
+init_per_group(GroupName, Config)
+ when (GroupName =:= sc_remote_close) orelse
+ (GroupName =:= sc_remote_shutdown) orelse
+ (GroupName =:= traffic) ->
+ io:format("init_per_group(~w) -> entry with"
+ "~n Config: ~p"
+ "~n", [GroupName, Config]),
+ %% Maybe we should skip the entire suite for this platform,
+ %% but for now we just skip these groups, which seem to
+ %% have problems (slave node start).
+ %% As stated elsewhere, its not really Fedora 16, but
+ %% the *really* slow VM that is the issue.
+ try is_old_fedora16() of
+ ok ->
+ Config
+ catch
+ throw:{skip, _} = SKIP ->
+ SKIP
+ end;
+init_per_group(ttest = _GroupName, Config) ->
+ io:format("init_per_group(~w) -> entry with"
+ "~n Config: ~p"
+ "~n", [_GroupName, Config]),
+ ttest_manager_start(),
+ case lists:keysearch(esock_test_ttest_runtime, 1, Config) of
+ {value, _} ->
+ Config;
+ false ->
+ [{esock_test_ttest_runtime, which_ttest_runtime_env()} | Config]
+ end;
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(ttest = _GroupName, Config) ->
+ io:format("init_per_group(~w) -> entry with"
+ "~n Config: ~p"
+ "~n", [_GroupName, Config]),
+ ttest_manager_stop(),
+ lists:keydelete(esock_test_ttest_runtime, 1, Config);
+end_per_group(_GroupName, Config) ->
+ Config.
+
+
+init_per_testcase(_TC, Config) ->
+ io:format("init_per_testcase(~w) -> entry with"
+ "~n Config: ~p"
+ "~n", [_TC, Config]),
+ %% case quiet_mode(Config) of
+ %% default ->
+ %% ?LOGGER:start();
+ %% Quiet ->
+ %% ?LOGGER:start(Quiet)
+ %% end,
+ Config.
+
+end_per_testcase(_TC, Config) ->
+ %% ?LOGGER:stop(),
+ Config.
+
+
+quiet_mode(Config) ->
+ case lists:keysearch(esock_test_quiet, 1, Config) of
+ {value, {esock_test_quiet, Quiet}} ->
+ Quiet;
+ false ->
+ case os:getenv("ESOCK_TEST_QUIET") of
+ "true" -> true;
+ "false" -> false;
+ _ -> default
+ end
+ end.
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% %%
+%% API MISC %%
+%% %%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This is an extremely rudimentary test case, that just tests
+%% that we can call the "global" info function and that it returns
+%% a non-empty map...
+
+api_m_info(suite) ->
+ [];
+api_m_info(doc) ->
+ [];
+api_m_info(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_m_info,
+ fun() ->
+ ok = api_m_info()
+ end).
+
+api_m_info() ->
+ case socket:info() of
+ Info when is_map(Info) ->
+ Sz = maps:size(Info),
+ if
+ (Sz > 0) ->
+ ok;
+ true ->
+ ?FAIL(no_info)
+ end;
+ Info ->
+ ?FAIL({invalid_info, Info})
+ end.
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% A simple test case that tests that the global debug can be changed.
+%% At the same time, it will test the info function (since it uses it
+%% for verification).
+
+api_m_debug(suite) ->
+ [];
+api_m_debug(doc) ->
+ [];
+api_m_debug(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_m_debug,
+ fun() -> has_bugfree_gcc() end,
+ fun() ->
+ ok = api_m_debug()
+ end).
+
+%% For some reason this test case triggers a gcc bug, which causes
+%% a segfault, on an ancient Fedora 16 VM. So, check the version of gcc...
+%% Not pretty, but the simplest way to skip (without actually testing
+%% for the host).
+has_bugfree_gcc() ->
+ has_bugfree_gcc(os:type()).
+
+%% Make sure we are on linux
+has_bugfree_gcc({unix, linux}) ->
+ has_bugfree_gcc2(string:trim(os:cmd("cat /etc/issue")));
+has_bugfree_gcc(_) ->
+ ok.
+
+%% Make sure we are on Fedora 16
+has_bugfree_gcc2("Fedora release 16 " ++ _) ->
+ has_bugfree_gcc3(os:cmd("gcc --version"));
+has_bugfree_gcc2("Welcome to SUSE Linux " ++ _) ->
+ has_bugfree_gcc4(os:cmd("gcc --version"));
+has_bugfree_gcc2(_) ->
+ ok.
+
+has_bugfree_gcc3("gcc (GCC) 4.6.3 20120306 (Red Hat 4.6.3-2" ++ _) ->
+ skip("Buggy GCC");
+has_bugfree_gcc3(_) ->
+ ok.
+
+has_bugfree_gcc4("gcc (SUSE Linux) 4.3.2" ++ _) ->
+ skip("Buggy GCC");
+has_bugfree_gcc4(_) ->
+ ok.
+
+api_m_debug() ->
+ i("get initial info"),
+ #{debug := D0} = socket:info(),
+ D1 = not D0,
+ i("set new debug (~w => ~w)", [D0, D1]),
+ ok = socket:debug(D1),
+ i("get updated info (~w)", [D1]),
+ #{debug := D1} = socket:info(),
+ D2 = not D1,
+ i("set new debug (~w => ~w)", [D1, D2]),
+ ok = socket:debug(D2),
+ i("get updated info (~w)", [D2]),
+ #{debug := D2} = socket:info(),
+ i("ok"),
+ ok.
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% %%
+%% API BASIC %%
+%% %%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) and info of an IPv4 UDP (dgram) socket.
+%% With some extra checks...
+api_b_open_and_info_udp4(suite) ->
+ [];
+api_b_open_and_info_udp4(doc) ->
+ [];
+api_b_open_and_info_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_b_open_and_info_udp4,
+ fun() ->
+ InitState = #{domain => inet,
+ type => dgram,
+ protocol => udp},
+ ok = api_b_open_and_info(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) and info of an IPv6 UDP (dgram) socket.
+%% With some extra checks...
+api_b_open_and_info_udp6(suite) ->
+ [];
+api_b_open_and_info_udp6(doc) ->
+ [];
+api_b_open_and_info_udp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_b_open_and_info_udp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet6,
+ type => dgram,
+ protocol => udp},
+ ok = api_b_open_and_info(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) and info of an IPv4 TCP (stream) socket.
+%% With some extra checks...
+api_b_open_and_info_tcp4(suite) ->
+ [];
+api_b_open_and_info_tcp4(doc) ->
+ [];
+api_b_open_and_info_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_b_open_and_info_tcp4,
+ fun() ->
+ InitState = #{domain => inet,
+ type => stream,
+ protocol => tcp},
+ ok = api_b_open_and_info(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) and info of an IPv6 TCP (stream) socket.
+%% With some extra checks...
+api_b_open_and_info_tcp6(suite) ->
+ [];
+api_b_open_and_info_tcp6(doc) ->
+ [];
+api_b_open_and_info_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_b_open_and_info_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet6,
+ type => stream,
+ protocol => tcp},
+ ok = api_b_open_and_info(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_b_open_and_info(InitState) ->
+ Seq =
+ [
+ #{desc => "open",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Protocol} = State) ->
+ case socket:open(Domain, Type, Protocol) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "get socket info",
+ cmd => fun(#{sock := Sock} = State) ->
+ Info = socket:info(Sock),
+ ?SEV_IPRINT("Got (some) Info: "
+ "~n ~p", [Info]),
+ {ok, State#{info => Info}}
+ end},
+ #{desc => "validate socket info",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Protocol,
+ info := #{domain := Domain,
+ type := Type,
+ protocol := Protocol,
+ counters := _,
+ num_readers := 0,
+ num_writers := 0,
+ num_acceptors := 0}}) ->
+ ok;
+ (#{domain := Domain,
+ type := Type,
+ protocol := Protocol,
+ info := Info}) ->
+ ?SEV_EPRINT("Unexpected Info: "
+ "~n (expected) Domain: ~p"
+ "~n (expected) Type: ~p"
+ "~n (expected) Protocol: ~p"
+ "~n ~p",
+ [Domain, Type, Protocol, Info]),
+ {error, unexpected_infio}
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = _State) ->
+ socket:close(Sock)
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+ Evaluator = ?SEV_START("tester", Seq, InitState),
+ ok = ?SEV_AWAIT_FINISH([Evaluator]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) and close an IPv4 UDP (dgram) socket.
+%% With some extra checks...
+api_b_open_and_close_udp4(suite) ->
+ [];
+api_b_open_and_close_udp4(doc) ->
+ [];
+api_b_open_and_close_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_b_open_and_close_udp4,
+ fun() ->
+ InitState = #{domain => inet,
+ type => dgram,
+ protocol => udp},
+ ok = api_b_open_and_close(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) and close an IPv6 UDP (dgram) socket.
+%% With some extra checks...
+api_b_open_and_close_udp6(suite) ->
+ [];
+api_b_open_and_close_udp6(doc) ->
+ [];
+api_b_open_and_close_udp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_b_open_and_close_udp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet6,
+ type => dgram,
+ protocol => udp},
+ ok = api_b_open_and_close(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) and close an IPv4 TCP (stream) socket.
+%% With some extra checks...
+api_b_open_and_close_tcp4(suite) ->
+ [];
+api_b_open_and_close_tcp4(doc) ->
+ [];
+api_b_open_and_close_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_b_open_and_close_tcp4,
+ fun() ->
+ InitState = #{domain => inet,
+ type => stream,
+ protocol => tcp},
+ ok = api_b_open_and_close(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) and close an IPv6 TCP (stream) socket.
+%% With some extra checks...
+api_b_open_and_close_tcp6(suite) ->
+ [];
+api_b_open_and_close_tcp6(doc) ->
+ [];
+api_b_open_and_close_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_b_open_and_close_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet,
+ type => stream,
+ protocol => tcp},
+ ok = api_b_open_and_close(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) and close an Unix Domain dgram (UDP) socket.
+%% With some extra checks...
+api_b_open_and_close_udpL(suite) ->
+ [];
+api_b_open_and_close_udpL(doc) ->
+ [];
+api_b_open_and_close_udpL(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_b_open_and_close_udpL,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ InitState = #{domain => local,
+ type => dgram,
+ protocol => default},
+ ok = api_b_open_and_close(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) and close an Unix Domain stream (TCP) socket.
+%% With some extra checks...
+api_b_open_and_close_tcpL(suite) ->
+ [];
+api_b_open_and_close_tcpL(doc) ->
+ [];
+api_b_open_and_close_tcpL(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_b_open_and_close_tcpL,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ InitState = #{domain => local,
+ type => stream,
+ protocol => default},
+ ok = api_b_open_and_close(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) and close an Unix Domain dgram (UDP) socket.
+%% With some extra checks...
+api_b_open_and_close_seqpL(suite) ->
+ [];
+api_b_open_and_close_seqpL(doc) ->
+ [];
+api_b_open_and_close_seqpL(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(?FUNCTION_NAME,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ InitState = #{domain => local,
+ type => seqpacket,
+ protocol => default},
+ ok = api_b_open_and_close(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) and close an IPv4 SCTP (seqpacket) socket.
+%% With some extra checks...
+api_b_open_and_close_sctp4(suite) ->
+ [];
+api_b_open_and_close_sctp4(doc) ->
+ [];
+api_b_open_and_close_sctp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_b_open_and_close_sctp4,
+ fun() -> has_support_sctp() end,
+ fun() ->
+ InitState = #{domain => inet,
+ type => seqpacket,
+ protocol => sctp},
+ ok = api_b_open_and_close(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_b_open_and_close(InitState) ->
+ Seq =
+ [
+ #{desc => "open",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Protocol} = S) ->
+ Res = socket:open(Domain, Type, Protocol),
+ {ok, {S, Res}}
+ end},
+ #{desc => "validate open",
+ cmd => fun({S, {ok, Sock}}) ->
+ NewS = S#{socket => Sock},
+ {ok, NewS};
+ ({_, {error, epfnosupport = Reason}}) ->
+ {skip, Reason};
+ ({_, {error, eprotonosupport = Reason}}) ->
+ {skip, Reason};
+ ({_, {error, esocktnosupport = Reason}}) ->
+ {skip, Reason};
+ ({_, {error, _} = ERROR}) ->
+ ERROR
+ end},
+ #{desc => "get domain (maybe)",
+ cmd => fun(#{socket := Sock} = S) ->
+ Res = socket:getopt(Sock, socket, domain),
+ {ok, {S, Res}}
+ end},
+ #{desc => "validate domain (maybe)",
+ cmd => fun({#{domain := Domain} = S, {ok, Domain}}) ->
+ {ok, S};
+ ({#{domain := ExpDomain}, {ok, Domain}}) ->
+ {error, {unexpected_domain, ExpDomain, Domain}};
+ %% Some platforms do not support this option
+ ({S, {error, einval}}) ->
+ {ok, S};
+ ({_, {error, _} = ERROR}) ->
+ ERROR
+ end},
+ #{desc => "get type",
+ cmd => fun(#{socket := Sock} = State) ->
+ Res = socket:getopt(Sock, socket, type),
+ {ok, {State, Res}}
+ end},
+ #{desc => "validate type",
+ cmd => fun({#{type := Type} = State, {ok, Type}}) ->
+ {ok, State};
+ ({#{type := ExpType}, {ok, Type}}) ->
+ {error, {unexpected_type, ExpType, Type}};
+ ({_, {error, _} = ERROR}) ->
+ ERROR
+ end},
+ #{desc => "get protocol",
+ cmd => fun(#{socket := Sock} = State) ->
+ case socket:is_supported(options, socket, protocol) of
+ true ->
+ Res = socket:getopt(Sock, socket, protocol),
+ {ok, {State, Res}};
+ false ->
+ {ok, {State, not_supported}}
+ end
+ end},
+ #{desc => "validate protocol",
+ cmd => fun({State, not_supported}) ->
+ ?SEV_IPRINT("socket option 'protocol' "
+ "not supported"),
+ {ok, State};
+ ({#{protocol := Protocol} = State, {ok, Protocol}}) ->
+ {ok, State};
+ ({#{domain := Domain,
+ protocol := ExpProtocol}, {ok, Protocol}}) ->
+ %% On OpenBSD (at least 6.6) something screwy happens
+ %% when domain = local.
+ %% It will report a completly different protocol (icmp)
+ %% but everything still works. So we skip if this happens
+ %% on OpenBSD...
+ case os:type() of
+ {unix, openbsd} when (Domain =:= local) ->
+ {skip, ?F("Unexpected protocol: ~p instead of ~p",
+ [Protocol, ExpProtocol])};
+ _ ->
+ {error, {unexpected_protocol,
+ ExpProtocol, Protocol}}
+ end;
+ ({_, {error, _} = ERROR}) ->
+ ERROR
+ end},
+ #{desc => "get controlling-process",
+ cmd => fun(#{socket := Sock} = State) ->
+ Res = socket:getopt(Sock, otp, controlling_process),
+ {ok, {State, Res}}
+ end},
+ #{desc => "validate controlling-process",
+ cmd => fun({State, {ok, Pid}}) ->
+ case self() of
+ Pid ->
+ {ok, State};
+ _ ->
+ {error, {unexpected_owner, Pid}}
+ end;
+ ({_, {error, _} = ERROR}) ->
+ ERROR
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{socket := Sock} = State) ->
+ Res = socket:close(Sock),
+ {ok, {State, Res}}
+ end},
+ #{desc => "validate socket close",
+ cmd => fun({_, ok}) ->
+ ok;
+ ({_, {error, _} = ERROR}) ->
+ ERROR
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+ Evaluator = ?SEV_START("tester", Seq, InitState),
+ ok = ?SEV_AWAIT_FINISH([Evaluator]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) and (maybe) close an RAW socket.
+
+api_b_open_and_maybe_close_raw(suite) ->
+ [];
+api_b_open_and_maybe_close_raw(doc) ->
+ [];
+api_b_open_and_maybe_close_raw(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_b_open_and_maybe_close_raw,
+ fun() ->
+ InitState = #{domain => inet,
+ type => raw,
+ protocol => {raw, 255}},
+ ok = do_api_b_open_and_maybe_close_raw(InitState)
+ end).
+
+do_api_b_open_and_maybe_close_raw(InitState) ->
+ Tester =
+ [
+ #{desc => "open",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Protocol} = State) ->
+ ?SEV_IPRINT("try open with:"
+ "~n Domain: ~p"
+ "~n Type: ~p"
+ "~n Protocol: ~p",
+ [Domain, Type, Protocol]),
+ case socket:open(Domain, Type, Protocol) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, Reason} when (Reason =:= eperm) orelse
+ (Reason =:= eacces) ->
+ ?SEV_IPRINT("not allowed (~w) => SKIP",
+ [Reason]),
+ {skip, Reason};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("open failed:"
+ "~n Reason: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{socket := Sock} = State) ->
+ ?SEV_IPRINT("try socket close"),
+ case socket:close(Sock) of
+ ok ->
+ ?SEV_IPRINT("socket closed"),
+ {ok, maps:remote(sock, State)};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("close failed:"
+ "~n Reason: ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+ Evaluator = ?SEV_START("tester", Tester, InitState),
+ ok = ?SEV_AWAIT_FINISH([Evaluator]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically send and receive on an IPv4 UDP (dgram) socket using
+%% sendto and recvfrom..
+api_b_sendto_and_recvfrom_udp4(suite) ->
+ [];
+api_b_sendto_and_recvfrom_udp4(doc) ->
+ [];
+api_b_sendto_and_recvfrom_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_b_sendto_and_recvfrom_udp4,
+ fun() ->
+ Send = fun(Sock, Data, Dest) ->
+ socket:sendto(Sock, Data, Dest)
+ end,
+ Recv = fun(Sock) ->
+ socket:recvfrom(Sock)
+ end,
+ InitState = #{domain => inet,
+ proto => udp,
+ send => Send,
+ recv => Recv},
+ ok = api_b_send_and_recv_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically send and receive on an IPv4 UDP (dgram) socket using
+%% sendto and recvfrom.
+api_b_sendto_and_recvfrom_udpL(suite) ->
+ [];
+api_b_sendto_and_recvfrom_udpL(doc) ->
+ [];
+api_b_sendto_and_recvfrom_udpL(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_b_sendto_and_recvfrom_udpL,
+ fun() ->
+ has_support_unix_domain_socket(),
+ unix_domain_socket_host_cond()
+ end,
+ fun() ->
+ Send = fun(Sock, Data, Dest) ->
+ socket:sendto(Sock, Data, Dest)
+ end,
+ Recv = fun(Sock) ->
+ socket:recvfrom(Sock)
+ end,
+ InitState = #{domain => local,
+ proto => default,
+ send => Send,
+ recv => Recv},
+ ok = api_b_send_and_recv_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically send and receive on an IPv4 UDP (dgram) socket
+%% using sendmsg and recvmsg.
+api_b_sendmsg_and_recvmsg_udp4(suite) ->
+ [];
+api_b_sendmsg_and_recvmsg_udp4(doc) ->
+ [];
+api_b_sendmsg_and_recvmsg_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_b_sendmsg_and_recvmsg_udp4,
+ fun() ->
+ Send = fun(Sock, Data, Dest) ->
+ %% We need tests for this,
+ %% but this is not the place it.
+ %% CMsgHdr = #{level => ip,
+ %% type => tos,
+ %% data => reliability},
+ %% CMsgHdrs = [CMsgHdr],
+ MsgHdr = #{addr => Dest,
+ %% ctrl => CMsgHdrs,
+ iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ Recv = fun(Sock) ->
+ %% We have some issues on old darwing...
+ %% socket:setopt(Sock, otp, debug, true),
+ case socket:recvmsg(Sock) of
+ {ok, #{addr := Source,
+ iov := [Data]}} ->
+ %% socket:setopt(Sock, otp, debug, false),
+ {ok, {Source, Data}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet,
+ proto => udp,
+ send => Send,
+ recv => Recv},
+ ok = api_b_send_and_recv_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically send and receive on an IPv4 UDP (dgram) socket
+%% using sendmsg and recvmsg.
+api_b_sendmsg_and_recvmsg_udpL(suite) ->
+ [];
+api_b_sendmsg_and_recvmsg_udpL(doc) ->
+ [];
+api_b_sendmsg_and_recvmsg_udpL(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_b_sendmsg_and_recvmsg_udpL,
+ fun() ->
+ has_support_unix_domain_socket(),
+ unix_domain_socket_host_cond()
+ end,
+ fun() ->
+ Send = fun(Sock, Data, Dest) ->
+ %% We need tests for this,
+ %% but this is not the place it.
+ %% CMsgHdr = #{level => ip,
+ %% type => tos,
+ %% data => reliability},
+ %% CMsgHdrs = [CMsgHdr],
+ MsgHdr = #{addr => Dest,
+ %% ctrl => CMsgHdrs,
+ iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ Recv = fun(Sock) ->
+ %% We have some issues on old darwing...
+ %% socket:setopt(Sock, otp, debug, true),
+ case socket:recvmsg(Sock) of
+ {ok, #{addr := Source,
+ iov := [Data]}} ->
+ %% socket:setopt(Sock, otp, debug, false),
+ {ok, {Source, Data}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => local,
+ proto => default,
+ send => Send,
+ recv => Recv},
+ ok = api_b_send_and_recv_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_b_send_and_recv_udp(InitState) ->
+ Seq =
+ [
+ #{desc => "local address",
+ cmd => fun(#{domain := local = Domain} = State) ->
+ LSASrc = which_local_socket_addr(Domain),
+ LSADst = which_local_socket_addr(Domain),
+ {ok, State#{lsa_src => LSASrc,
+ lsa_dst => LSADst}};
+ (#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa_src => LSA,
+ lsa_dst => LSA}}
+ end},
+
+ #{desc => "open src socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ Sock = sock_open(Domain, dgram, Proto),
+ {ok, State#{sock_src => Sock}}
+ end},
+ #{desc => "bind src socket",
+ cmd => fun(#{sock_src := Sock, lsa_src := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ {ok, Port} ->
+ ?SEV_IPRINT("src bound (to ~p)", [Port]),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("src bind failed: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "sockname src socket",
+ cmd => fun(#{sock_src := Sock} = State) ->
+ SASrc = sock_sockname(Sock),
+ ?SEV_IPRINT("src sockaddr: "
+ "~n ~p", [SASrc]),
+ {ok, State#{sa_src => SASrc}}
+ end},
+
+ #{desc => "open dst socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ Sock = sock_open(Domain, dgram, Proto),
+ {ok, State#{sock_dst => Sock}}
+ end},
+ #{desc => "bind dst socket",
+ cmd => fun(#{sock_dst := Sock, lsa_dst := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ {ok, Port} ->
+ ?SEV_IPRINT("src bound (to ~p)", [Port]),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("src bind failed: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "sockname dst socket",
+ cmd => fun(#{sock_dst := Sock} = State) ->
+ SADst = sock_sockname(Sock),
+ ?SEV_IPRINT("dst sockaddr: "
+ "~n ~p", [SADst]),
+ {ok, State#{sa_dst => SADst}}
+ end},
+ #{desc => "send req (to dst)",
+ cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
+ Send(Sock, ?BASIC_REQ, Dst)
+ end},
+ #{desc => "recv req (from src)",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Src, ?BASIC_REQ}} ->
+ ok;
+ {ok, UnexpData} ->
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+ #{desc => "send rep (to src)",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, send := Send}) ->
+ Send(Sock, ?BASIC_REP, Src)
+ end},
+ #{desc => "recv rep (from dst)",
+ cmd => fun(#{sock_src := Sock, sa_dst := Dst, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Dst, ?BASIC_REP}} ->
+ ok;
+ {ok, UnexpData} ->
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+ #{desc => "close src socket",
+ cmd => fun(#{domain := local,
+ sock_src := Sock,
+ lsa_src := #{path := Path}} = State) ->
+ ok = socket:close(Sock),
+ State1 =
+ unlink_path(Path,
+ fun() -> maps:remove(lsa_src, State) end,
+ fun() -> State end),
+ {ok, maps:remove(sock_src, State1)};
+ (#{sock_src := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock_src, State)}
+ end},
+ #{desc => "close dst socket",
+ cmd => fun(#{domain := local,
+ sock_dst := Sock,
+ lsa_dst := #{path := Path}} = State) ->
+ ok = socket:close(Sock),
+ State1 =
+ unlink_path(Path,
+ fun() -> maps:remove(lsa_dst, State) end,
+ fun() -> State end),
+ {ok, maps:remove(sock_dst, State1)};
+ (#{sock_dst := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock_dst, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+ Evaluator = ?SEV_START("tester", Seq, InitState),
+ ok = ?SEV_AWAIT_FINISH([Evaluator]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically send and receive using the "common" functions (send and recv)
+%% on an IPv4 TCP (stream) socket.
+api_b_send_and_recv_tcp4(suite) ->
+ [];
+api_b_send_and_recv_tcp4(doc) ->
+ [];
+api_b_send_and_recv_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_b_send_and_recv_tcp4,
+ fun() ->
+ Send = fun(Sock, Data) ->
+ socket:send(Sock, Data)
+ end,
+ Recv = fun(Sock) ->
+ socket:recv(Sock)
+ end,
+ InitState = #{domain => inet,
+ type => stream,
+ proto => tcp,
+ send => Send,
+ recv => Recv},
+ ok = api_b_send_and_recv_conn(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically send and receive using the "common" functions (send and recv)
+%% on an Unix Domain (stream) socket (TCP).
+api_b_send_and_recv_tcpL(suite) ->
+ [];
+api_b_send_and_recv_tcpL(doc) ->
+ [];
+api_b_send_and_recv_tcpL(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_b_send_and_recv_tcpL,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ Send = fun(Sock, Data) ->
+ socket:send(Sock, Data)
+ end,
+ Recv = fun(Sock) ->
+ socket:recv(Sock)
+ end,
+ InitState = #{domain => local,
+ type => stream,
+ proto => default,
+ send => Send,
+ recv => Recv},
+ ok = api_b_send_and_recv_conn(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically send and receive using the "common" functions (send and recv)
+%% on an Unix Domain seqpacket socket.
+api_b_send_and_recv_seqpL(suite) ->
+ [];
+api_b_send_and_recv_seqpL(doc) ->
+ [];
+api_b_send_and_recv_seqpL(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(?FUNCTION_NAME,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ Send = fun(Sock, Data) ->
+ socket:send(Sock, Data)
+ end,
+ Recv = fun(Sock) ->
+ socket:recv(Sock)
+ end,
+ InitState = #{domain => local,
+ type => seqpacket,
+ proto => default,
+ send => Send,
+ recv => Recv},
+ ok = api_b_send_and_recv_conn(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically send and receive using the msg functions (sendmsg and recvmsg)
+%% on an IPv4 TCP (stream) socket.
+api_b_sendmsg_and_recvmsg_tcp4(suite) ->
+ [];
+api_b_sendmsg_and_recvmsg_tcp4(doc) ->
+ [];
+api_b_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_b_sendmsg_and_recvmsg_tcp4,
+ fun() ->
+ Send = fun(Sock, Data) ->
+ MsgHdr = #{iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock) of
+ {ok, #{addr := undefined,
+ iov := [Data]}} ->
+ {ok, Data};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet,
+ type => stream,
+ proto => tcp,
+ send => Send,
+ recv => Recv},
+ ok = api_b_send_and_recv_conn(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically send and receive using the msg functions (sendmsg and recvmsg)
+%% on an Unix Domain (stream) socket (TCP).
+api_b_sendmsg_and_recvmsg_tcpL(suite) ->
+ [];
+api_b_sendmsg_and_recvmsg_tcpL(doc) ->
+ [];
+api_b_sendmsg_and_recvmsg_tcpL(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_b_sendmsg_and_recvmsg_tcpL,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ Send = fun(Sock, Data) ->
+ MsgHdr = #{iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock) of
+ %% On some platforms, the address
+ %% is *not* provided (e.g. FreeBSD)
+ {ok, #{addr := undefined,
+ iov := [Data]}} ->
+ {ok, Data};
+ %% On some platforms, the address
+ %% *is* provided (e.g. linux)
+ {ok, #{addr := #{family := local},
+ iov := [Data]}} ->
+ socket:setopt(Sock,
+ otp,
+ debug,
+ false),
+ {ok, Data};
+ {error, _} = ERROR ->
+ socket:setopt(Sock,
+ otp,
+ debug,
+ false),
+ ERROR
+ end
+ end,
+ InitState = #{domain => local,
+ type => stream,
+ proto => default,
+ send => Send,
+ recv => Recv},
+ ok = api_b_send_and_recv_conn(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically send and receive using the msg functions (sendmsg and recvmsg)
+%% on an Unix Domain (stream) socket (TCP).
+api_b_sendmsg_and_recvmsg_seqpL(suite) ->
+ [];
+api_b_sendmsg_and_recvmsg_seqpL(doc) ->
+ [];
+api_b_sendmsg_and_recvmsg_seqpL(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(?FUNCTION_NAME,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ Send =
+ fun(Sock, Data) ->
+ MsgHdr =
+ #{iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock) of
+ %% On some platforms, the address
+ %% is *not* provided (e.g. FreeBSD)
+ {ok,
+ #{addr := undefined,
+ iov := [Data]}} ->
+ {ok, Data};
+ %% On some platforms, the address
+ %% *is* provided (e.g. linux)
+ {ok,
+ #{addr := #{family := local},
+ iov := [Data]}} ->
+ socket:setopt(
+ Sock, otp, debug, false),
+ {ok, Data};
+ {error, _} = ERROR ->
+ socket:setopt(
+ Sock, otp, debug, false),
+ ERROR
+ end
+ end,
+ InitState =
+ #{domain => local,
+ type => seqpacket,
+ proto => default,
+ send => Send,
+ recv => Recv},
+ ok = api_b_send_and_recv_conn(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_b_send_and_recv_conn(InitState) ->
+ process_flag(trap_exit, true),
+ ServerSeq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa => LSA}}
+ end},
+ #{desc => "create listen socket",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ proto := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, eprotonosupport = Reason} ->
+ {skip, Reason};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{domain := local,
+ lsock := LSock,
+ lsa := LSA} = _State) ->
+ case socket:bind(LSock, LSA) of
+ {ok, _Port} ->
+ ok; % We do not care about the port for local
+ {error, _} = ERROR ->
+ ERROR
+ end;
+ (#{lsock := LSock, lsa := LSA} = State) ->
+ case sock_bind(LSock, LSA) of
+ {ok, Port} ->
+ ?SEV_IPRINT("bound to port: ~w", [Port]),
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{domain := local,
+ tester := Tester, lsa := #{path := Path}}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Path),
+ ok;
+ (#{tester := Tester, lport := Port}) ->
+ %% This is actually not used for unix domain socket
+ ?SEV_ANNOUNCE_READY(Tester, init, Port),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "await connection",
+ cmd => fun(#{lsock := LSock} = State) ->
+ case socket:accept(LSock) of
+ {ok, Sock} ->
+ ?SEV_IPRINT("accepted: ~n ~p", [Sock]),
+ {ok, State#{csock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept),
+ ok
+ end},
+ #{desc => "await (recv) request",
+ cmd => fun(#{csock := Sock, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, ?BASIC_REQ} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv request)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_req),
+ ok
+ end},
+ #{desc => "await continue (with send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply)
+ end},
+ #{desc => "send reply",
+ cmd => fun(#{csock := Sock, send := Send}) ->
+ Send(Sock, ?BASIC_REP)
+ end},
+ #{desc => "announce ready (send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_reply),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close connection socket",
+ cmd => fun(#{csock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(csock, State)}
+ end},
+ #{desc => "close listen socket",
+ cmd => fun(#{domain := local,
+ lsock := Sock,
+ local_sa := #{path := Path}} = State) ->
+ ok = socket:close(Sock),
+ State1 =
+ unlink_path(Path,
+ fun() ->
+ maps:remove(local_sa, State)
+ end,
+ fun() -> State end),
+ {ok, maps:remove(lsock, State1)};
+ (#{lsock := LSock} = State) ->
+ case socket:close(LSock) of
+ ok ->
+ {ok, maps:remove(lsock, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ ClientSeq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(#{domain := local} = State) ->
+ {Tester, Path} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, server_path => Path}};
+ (State) ->
+ {Tester, Port} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, server_port => Port}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** The init part ***
+ #{desc => "which server (local) address",
+ cmd => fun(#{domain := local = Domain,
+ server_path := Path} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ SSA = #{family => Domain, path => Path},
+ {ok, State#{local_sa => LSA, server_sa => SSA}};
+ (#{domain := Domain, server_port := Port} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ SSA = LSA#{port => Port},
+ {ok, State#{local_sa => LSA, server_sa => SSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ proto := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{sock := Sock, local_sa := LSA} = _State) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% *** The actual test ***
+ #{desc => "await continue (connect)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, connect)
+ end},
+ #{desc => "connect to server",
+ cmd => fun(#{sock := Sock, server_sa := SSA}) ->
+ socket:connect(Sock, SSA)
+ end},
+ #{desc => "announce ready (connect)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, connect),
+ ok
+ end},
+ #{desc => "await continue (send request)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_req)
+ end},
+ #{desc => "send request (to server)",
+ cmd => fun(#{sock := Sock, send := Send}) ->
+ Send(Sock, ?BASIC_REQ)
+ end},
+ #{desc => "announce ready (send request)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_req),
+ ok
+ end},
+ #{desc => "await recv reply (from server)",
+ cmd => fun(#{sock := Sock, recv := Recv}) ->
+ {ok, ?BASIC_REP} = Recv(Sock),
+ ok
+ end},
+ #{desc => "announce ready (recv reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_reply),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{domain := local,
+ sock := Sock,
+ local_sa := #{path := Path}} = State) ->
+ ok = socket:close(Sock),
+ State1 =
+ unlink_path(Path,
+ fun() ->
+ maps:remove(local_sa, State)
+ end,
+ fun() -> State end),
+ {ok, maps:remove(sock, State1)};
+ (#{sock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = State) ->
+ {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{server_port => Port}}
+ end},
+
+ %% Start the client
+ #{desc => "order client start",
+ cmd => fun(#{client := Pid, server_port := Port} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Port),
+ ok
+ end},
+ #{desc => "await client ready (init)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client, init)
+ end},
+
+ %% *** The actual test ***
+ #{desc => "order server to continue (with accept)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, accept),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order client to continue (with connect)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, connect),
+ ok
+ end},
+ #{desc => "await client ready (connect)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, connect)
+ end},
+ #{desc => "await server ready (accept)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, accept)
+ end},
+ #{desc => "order client to continue (with send request)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send_req),
+ ok
+ end},
+ #{desc => "await client ready (with send request)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, send_req)
+ end},
+ #{desc => "await server ready (request recv)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv_req)
+ end},
+ #{desc => "order server to continue (with send reply)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, send_reply),
+ ok
+ end},
+ #{desc => "await server ready (with reply sent)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, send_reply)
+ end},
+ #{desc => "await client ready (reply recv)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, recv_reply)
+ end},
+
+
+ %% *** Termination ***
+ #{desc => "order client to terminate",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await client termination",
+ cmd => fun(#{client := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(client, State),
+ {ok, State1}
+ end},
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Server),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Server} = State) ->
+ ?SEV_AWAIT_TERMINATION(Server),
+ State1 = maps:remove(server, State),
+ {ok, State1}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start server evaluator"),
+ Server = ?SEV_START("server", ServerSeq, InitState),
+
+ i("start client evaluator"),
+ Client = ?SEV_START("client", ClientSeq, InitState),
+ i("await evaluator(s)"),
+
+ i("start tester evaluator"),
+ TesterInitState = #{server => Server#ev.pid,
+ client => Client#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically send and receive on an IPv4 SCTP (seqpacket) socket
+%% using sendmsg and recvmsg.
+api_b_sendmsg_and_recvmsg_sctp4(suite) ->
+ [];
+api_b_sendmsg_and_recvmsg_sctp4(doc) ->
+ [];
+api_b_sendmsg_and_recvmsg_sctp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_b_sendmsg_and_recvmsg_sctp4,
+ fun() ->
+ has_support_sctp(),
+ not_yet_implemented()
+ end,
+ fun() ->
+ Send = fun(Sock, Data) ->
+ %% CMsgHdr = #{level => sctp,
+ %% type => tos,
+ %% data => reliability},
+ %% CMsgHdrs = [CMsgHdr],
+ MsgHdr = #{%% ctrl => CMsgHdrs,
+ iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ Recv = fun(Sock) ->
+ %% We have some issues on old darwing...
+ %% socket:setopt(Sock, otp, debug, true),
+ case socket:recvmsg(Sock) of
+ {ok, #{addr := Source,
+ iov := [Data]}} ->
+ %% socket:setopt(Sock, otp, debug, false),
+ {ok, {Source, Data}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet,
+ proto => sctp,
+ send => Send,
+ recv => Recv},
+ ok = api_b_send_and_recv_sctp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_b_send_and_recv_sctp(_InitState) ->
+%% Seq =
+%% [
+%% #{desc => "local address",
+%% cmd => fun(#{domain := Domain} = State) ->
+%% LSA = which_local_socket_addr(Domain),
+%% {ok, State#{lsa_src => LSA,
+%% lsa_dst => LSA}}
+%% end},
+
+%% #{desc => "open src socket",
+%% cmd => fun(#{domain := Domain,
+%% proto := Proto} = State) ->
+%% Sock = sock_open(Domain, seqpacket, Proto),
+%% {ok, State#{sock_src => Sock}}
+%% end},
+%% #{desc => "bind src",
+%% cmd => fun(#{sock_src := Sock, lsa_src := LSA}) ->
+%% case socket:bind(Sock, LSA) of
+%% {ok, _Port} ->
+%% ?SEV_IPRINT("src bound"),
+%% ok;
+%% {error, Reason} = ERROR ->
+%% ?SEV_EPRINT("src bind failed: ~p", [Reason]),
+%% ERROR
+%% end
+%% end},
+%% #{desc => "sockname src socket",
+%% cmd => fun(#{sock_src := Sock} = State) ->
+%% SASrc = sock_sockname(Sock),
+%% ?SEV_IPRINT("src sockaddr: "
+%% "~n ~p", [SASrc]),
+%% {ok, State#{sa_src => SASrc}}
+%% end},
+
+%% #{desc => "open dst socket",
+%% cmd => fun(#{domain := Domain,
+%% proto := Proto} = State) ->
+%% Sock = sock_open(Domain, seqpacket, Proto),
+%% {ok, State#{sock_dst => Sock}}
+%% end},
+%% #{desc => "bind dst",
+%% cmd => fun(#{sock_dst := Sock, lsa_dst := LSA}) ->
+%% case socket:bind(Sock, LSA) of
+%% {ok, _Port} ->
+%% ?SEV_IPRINT("src bound"),
+%% ok;
+%% {error, Reason} = ERROR ->
+%% ?SEV_EPRINT("src bind failed: ~p", [Reason]),
+%% ERROR
+%% end
+%% end},
+%% #{desc => "sockname dst socket",
+%% cmd => fun(#{sock_dst := Sock} = State) ->
+%% SADst = sock_sockname(Sock),
+%% ?SEV_IPRINT("dst sockaddr: "
+%% "~n ~p", [SADst]),
+%% {ok, State#{sa_dst => SADst}}
+%% end},
+%% #{desc => "send req (to dst)",
+%% cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
+%% Send(Sock, ?BASIC_REQ, Dst)
+%% end},
+%% #{desc => "recv req (from src)",
+%% cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
+%% case Recv(Sock) of
+%% {ok, {Src, ?BASIC_REQ}} ->
+%% ok;
+%% {ok, UnexpData} ->
+%% {error, {unexpected_data, UnexpData}};
+%% {error, _} = ERROR ->
+%% %% At the moment there is no way to get
+%% %% status or state for the socket...
+%% ERROR
+%% end
+%% end},
+%% #{desc => "send rep (to src)",
+%% cmd => fun(#{sock_dst := Sock, sa_src := Src, send := Send}) ->
+%% Send(Sock, ?BASIC_REP, Src)
+%% end},
+%% #{desc => "recv rep (from dst)",
+%% cmd => fun(#{sock_src := Sock, sa_dst := Dst, recv := Recv}) ->
+%% case Recv(Sock) of
+%% {ok, {Dst, ?BASIC_REP}} ->
+%% ok;
+%% {ok, UnexpData} ->
+%% {error, {unexpected_data, UnexpData}};
+%% {error, _} = ERROR ->
+%% %% At the moment there is no way to get
+%% %% status or state for the socket...
+%% ERROR
+%% end
+%% end},
+%% #{desc => "close src socket",
+%% cmd => fun(#{sock_src := Sock} = State) ->
+%% ok = socket:close(Sock),
+%% {ok, maps:remove(sock_src, State)}
+%% end},
+%% #{desc => "close dst socket",
+%% cmd => fun(#{sock_dst := Sock} = State) ->
+%% ok = socket:close(Sock),
+%% {ok, maps:remove(sock_dst, State)}
+%% end},
+
+%% %% *** We are done ***
+%% ?SEV_FINISH_NORMAL
+%% ],
+%% Evaluator = ?SEV_START("tester", Seq, InitState),
+%% ok = ?SEV_AWAIT_FINISH([Evaluator]).
+
+%% process_flag(trap_exit, true),
+%% ServerSeq =
+%% [
+%% %% *** Wait for start order ***
+%% #{desc => "await start (from tester)",
+%% cmd => fun(State) ->
+%% Tester = ?SEV_AWAIT_START(),
+%% {ok, State#{tester => Tester}}
+%% end},
+%% #{desc => "monitor tester",
+%% cmd => fun(#{tester := Tester}) ->
+%% _MRef = erlang:monitor(process, Tester),
+%% ok
+%% end},
+
+%% %% *** Init part ***
+%% #{desc => "which local address",
+%% cmd => fun(#{domain := Domain} = State) ->
+%% LSA = which_local_socket_addr(Domain),
+%% {ok, State#{lsa => LSA}}
+%% end},
+%% #{desc => "create (listen) socket",
+%% cmd => fun(#{domain := Domain,
+%% proto := Proto} = State) ->
+%% case socket:open(Domain, seqpacket, Proto) of
+%% {ok, Sock} ->
+%% {ok, State#{lsock => Sock}};
+%% {error, _} = ERROR ->
+%% ERROR
+%% end
+%% end},
+%% #{desc => "bind to local address",
+%% cmd => fun(#{lsock := LSock, lsa := LSA} = State) ->
+%% case socket:bind(LSock, LSA) of
+%% {ok, Port} ->
+%% ?SEV_IPRINT("bound to port: ~w", [Port]),
+%% {ok, State#{lport => Port}};
+%% {error, _} = ERROR ->
+%% ERROR
+%% end
+%% end},
+%% #{desc => "make listen socket",
+%% cmd => fun(#{lsock := LSock}) ->
+%% socket:listen(LSock)
+%% end},
+%% #{desc => "announce ready (init)",
+%% cmd => fun(#{tester := Tester, lport := Port}) ->
+%% ?SEV_ANNOUNCE_READY(Tester, init, Port),
+%% ok
+%% end},
+
+%% %% The actual test
+%% #{desc => "await continue (accept)",
+%% cmd => fun(#{tester := Tester}) ->
+%% ?SEV_AWAIT_CONTINUE(Tester, tester, accept)
+%% end},
+%% #{desc => "accepting (await connection) FAKE",
+%% cmd => fun(#{lsock := LSock} = State) ->
+%% {ok, State#{csock => LSock}}
+%% end},
+%% %% #{desc => "accepting (await connection)",
+%% %% cmd => fun(#{lsock := LSock} = State) ->
+%% %% socket:setopt(LSock, otp, debug, true),
+%% %% case socket:accept(LSock) of
+%% %% {ok, Sock} ->
+%% %% socket:setopt(LSock, otp, debug, false),
+%% %% ?SEV_IPRINT("accepted: ~n ~p", [Sock]),
+%% %% {ok, State#{csock => Sock}};
+%% %% {error, Reason} = ERROR ->
+%% %% socket:setopt(LSock, otp, debug, false),
+%% %% ?SEV_EPRINT("Failed accepting: "
+%% %% "~n ~p", [Reason]),
+%% %% ERROR
+%% %% end
+%% %% end},
+%% #{desc => "announce ready (accept)",
+%% cmd => fun(#{tester := Tester}) ->
+%% ?SEV_ANNOUNCE_READY(Tester, accept),
+%% ok
+%% end},
+%% #{desc => "await (recv) request",
+%% cmd => fun(#{csock := Sock, recv := Recv}) ->
+%% case Recv(Sock) of
+%% {ok, ?BASIC_REQ} ->
+%% ok;
+%% {error, _} = ERROR ->
+%% ERROR
+%% end
+%% end},
+%% #{desc => "announce ready (recv request)",
+%% cmd => fun(#{tester := Tester}) ->
+%% ?SEV_ANNOUNCE_READY(Tester, recv_req),
+%% ok
+%% end},
+%% #{desc => "await continue (with send reply)",
+%% cmd => fun(#{tester := Tester}) ->
+%% ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply)
+%% end},
+%% #{desc => "send reply",
+%% cmd => fun(#{csock := Sock, send := Send}) ->
+%% Send(Sock, ?BASIC_REP)
+%% end},
+%% #{desc => "announce ready (send reply)",
+%% cmd => fun(#{tester := Tester}) ->
+%% ?SEV_ANNOUNCE_READY(Tester, send_reply),
+%% ok
+%% end},
+
+%% %% *** Termination ***
+%% #{desc => "await terminate",
+%% cmd => fun(#{tester := Tester} = State) ->
+%% case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+%% ok ->
+%% {ok, maps:remove(tester, State)};
+%% {error, _} = ERROR ->
+%% ERROR
+%% end
+%% end},
+%% #{desc => "close connection socket",
+%% cmd => fun(#{csock := Sock} = State) ->
+%% ok = socket:close(Sock),
+%% {ok, maps:remove(csock, State)}
+%% end},
+%% #{desc => "close listen socket",
+%% cmd => fun(#{lsock := LSock} = State) ->
+%% case socket:close(LSock) of
+%% ok ->
+%% {ok, maps:remove(lsock, State)};
+%% {error, _} = ERROR ->
+%% ERROR
+%% end
+%% end},
+
+%% %% *** We are done ***
+%% ?SEV_FINISH_NORMAL
+%% ],
+
+%% ClientSeq =
+%% [
+%% %% *** Wait for start order ***
+%% #{desc => "await start (from tester)",
+%% cmd => fun(State) ->
+%% {Tester, Port} = ?SEV_AWAIT_START(),
+%% {ok, State#{tester => Tester, server_port => Port}}
+%% end},
+%% #{desc => "monitor tester",
+%% cmd => fun(#{tester := Tester}) ->
+%% _MRef = erlang:monitor(process, Tester),
+%% ok
+%% end},
+
+%% %% *** The init part ***
+%% #{desc => "which server (local) address",
+%% cmd => fun(#{domain := Domain, server_port := Port} = State) ->
+%% LSA = which_local_socket_addr(Domain),
+%% SSA = LSA#{port => Port},
+%% {ok, State#{local_sa => LSA, server_sa => SSA}}
+%% end},
+%% #{desc => "create socket",
+%% cmd => fun(#{domain := Domain,
+%% proto := Proto} = State) ->
+%% case socket:open(Domain, seqpacket, Proto) of
+%% {ok, Sock} ->
+%% {ok, State#{sock => Sock}};
+%% {error, _} = ERROR ->
+%% ERROR
+%% end
+%% end},
+%% #{desc => "bind to local address",
+%% cmd => fun(#{sock := Sock, local_sa := LSA} = _State) ->
+%% case socket:bind(Sock, LSA) of
+%% {ok, _Port} ->
+%% ok;
+%% {error, _} = ERROR ->
+%% ERROR
+%% end
+%% end},
+%% #{desc => "announce ready (init)",
+%% cmd => fun(#{tester := Tester}) ->
+%% ?SEV_ANNOUNCE_READY(Tester, init),
+%% ok
+%% end},
+
+%% %% *** The actual test ***
+%% #{desc => "await continue (connect)",
+%% cmd => fun(#{tester := Tester} = _State) ->
+%% ?SEV_AWAIT_CONTINUE(Tester, tester, connect)
+%% end},
+%% #{desc => "connect to server",
+%% cmd => fun(#{sock := Sock, server_sa := SSA}) ->
+%% case socket:connect(Sock, SSA) of
+%% ok ->
+%% ?SEV_IPRINT("connected"),
+%% ok;
+%% {error, Reason} = ERROR ->
+%% ?SEV_EPRINT("Failed connect: "
+%% "~n ~p", [Reason]),
+%% ERROR
+%% end
+%% end},
+%% #{desc => "announce ready (connect)",
+%% cmd => fun(#{tester := Tester}) ->
+%% ?SEV_ANNOUNCE_READY(Tester, connect),
+%% ok
+%% end},
+%% #{desc => "await continue (send request)",
+%% cmd => fun(#{tester := Tester} = _State) ->
+%% ?SEV_AWAIT_CONTINUE(Tester, tester, send_req)
+%% end},
+%% #{desc => "send request (to server)",
+%% cmd => fun(#{sock := Sock, send := Send}) ->
+%% Send(Sock, ?BASIC_REQ)
+%% end},
+%% #{desc => "announce ready (send request)",
+%% cmd => fun(#{tester := Tester}) ->
+%% ?SEV_ANNOUNCE_READY(Tester, send_req),
+%% ok
+%% end},
+%% #{desc => "await recv reply (from server)",
+%% cmd => fun(#{sock := Sock, recv := Recv}) ->
+%% {ok, ?BASIC_REP} = Recv(Sock),
+%% ok
+%% end},
+%% #{desc => "announce ready (recv reply)",
+%% cmd => fun(#{tester := Tester}) ->
+%% ?SEV_ANNOUNCE_READY(Tester, recv_reply),
+%% ok
+%% end},
+
+%% %% *** Termination ***
+%% #{desc => "await terminate",
+%% cmd => fun(#{tester := Tester} = State) ->
+%% case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+%% ok ->
+%% {ok, maps:remove(tester, State)};
+%% {error, _} = ERROR ->
+%% ERROR
+%% end
+%% end},
+%% #{desc => "close socket",
+%% cmd => fun(#{sock := Sock} = State) ->
+%% ok = socket:close(Sock),
+%% {ok, maps:remove(sock, State)}
+%% end},
+
+%% %% *** We are done ***
+%% ?SEV_FINISH_NORMAL
+%% ],
+
+%% TesterSeq =
+%% [
+%% %% *** Init part ***
+%% #{desc => "monitor server",
+%% cmd => fun(#{server := Pid} = _State) ->
+%% _MRef = erlang:monitor(process, Pid),
+%% ok
+%% end},
+%% #{desc => "monitor client",
+%% cmd => fun(#{client := Pid} = _State) ->
+%% _MRef = erlang:monitor(process, Pid),
+%% ok
+%% end},
+
+%% %% Start the server
+%% #{desc => "order server start",
+%% cmd => fun(#{server := Pid} = _State) ->
+%% ?SEV_ANNOUNCE_START(Pid),
+%% ok
+%% end},
+%% #{desc => "await server ready (init)",
+%% cmd => fun(#{server := Pid} = State) ->
+%% {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init),
+%% {ok, State#{server_port => Port}}
+%% end},
+
+%% %% Start the client
+%% #{desc => "order client start",
+%% cmd => fun(#{client := Pid, server_port := Port} = _State) ->
+%% ?SEV_ANNOUNCE_START(Pid, Port),
+%% ok
+%% end},
+%% #{desc => "await client ready (init)",
+%% cmd => fun(#{client := Pid} = _State) ->
+%% ok = ?SEV_AWAIT_READY(Pid, client, init)
+%% end},
+
+%% %% *** The actual test ***
+%% #{desc => "order server to continue (with accept)",
+%% cmd => fun(#{server := Server} = _State) ->
+%% ?SEV_ANNOUNCE_CONTINUE(Server, accept),
+%% ok
+%% end},
+%% ?SEV_SLEEP(?SECS(1)),
+%% #{desc => "order client to continue (with connect)",
+%% cmd => fun(#{client := Client} = _State) ->
+%% ?SEV_ANNOUNCE_CONTINUE(Client, connect),
+%% ok
+%% end},
+%% #{desc => "await client ready (connect)",
+%% cmd => fun(#{client := Client} = _State) ->
+%% ?SEV_AWAIT_READY(Client, client, connect)
+%% end},
+%% #{desc => "await server ready (accept)",
+%% cmd => fun(#{server := Server} = _State) ->
+%% ?SEV_AWAIT_READY(Server, server, accept)
+%% end},
+%% #{desc => "order client to continue (with send request)",
+%% cmd => fun(#{client := Client} = _State) ->
+%% ?SEV_ANNOUNCE_CONTINUE(Client, send_req),
+%% ok
+%% end},
+%% #{desc => "await client ready (with send request)",
+%% cmd => fun(#{client := Client} = _State) ->
+%% ?SEV_AWAIT_READY(Client, client, send_req)
+%% end},
+%% #{desc => "await server ready (request recv)",
+%% cmd => fun(#{server := Server} = _State) ->
+%% ?SEV_AWAIT_READY(Server, server, recv_req)
+%% end},
+%% #{desc => "order server to continue (with send reply)",
+%% cmd => fun(#{server := Server} = _State) ->
+%% ?SEV_ANNOUNCE_CONTINUE(Server, send_reply),
+%% ok
+%% end},
+%% #{desc => "await server ready (with reply sent)",
+%% cmd => fun(#{server := Server} = _State) ->
+%% ?SEV_AWAIT_READY(Server, server, send_reply)
+%% end},
+%% #{desc => "await client ready (reply recv)",
+%% cmd => fun(#{client := Client} = _State) ->
+%% ?SEV_AWAIT_READY(Client, client, recv_reply)
+%% end},
+
+
+%% %% *** Termination ***
+%% #{desc => "order client to terminate",
+%% cmd => fun(#{client := Client} = _State) ->
+%% ?SEV_ANNOUNCE_TERMINATE(Client),
+%% ok
+%% end},
+%% #{desc => "await client termination",
+%% cmd => fun(#{client := Client} = State) ->
+%% ?SEV_AWAIT_TERMINATION(Client),
+%% State1 = maps:remove(client, State),
+%% {ok, State1}
+%% end},
+%% #{desc => "order server to terminate",
+%% cmd => fun(#{server := Server} = _State) ->
+%% ?SEV_ANNOUNCE_TERMINATE(Server),
+%% ok
+%% end},
+%% #{desc => "await server termination",
+%% cmd => fun(#{server := Server} = State) ->
+%% ?SEV_AWAIT_TERMINATION(Server),
+%% State1 = maps:remove(server, State),
+%% {ok, State1}
+%% end},
+
+%% %% *** We are done ***
+%% ?SEV_FINISH_NORMAL
+%% ],
+
+%% i("start server evaluator"),
+%% Server = ?SEV_START("server", ServerSeq, InitState),
+
+%% i("start client evaluator"),
+%% Client = ?SEV_START("client", ClientSeq, InitState),
+%% i("await evaluator(s)"),
+
+%% i("start tester evaluator"),
+%% TesterInitState = #{server => Server#ev.pid,
+%% client => Client#ev.pid},
+%% Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+%% ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]).
+
+ ok.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% %%
+%% API FROM FD %%
+%% %%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) a socket from an already existing
+%% file descriptor (FD) and info of an IPv4 UDP (dgram) socket.
+%% With some extra checks...
+%% IPv4
+%% Without dup
+api_ffd_open_wod_and_info_udp4(suite) ->
+ [];
+api_ffd_open_wod_and_info_udp4(doc) ->
+ [];
+api_ffd_open_wod_and_info_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_ffd_open_wod_and_info_udp4,
+ fun() ->
+ InitState = #{domain => inet,
+ type => dgram,
+ protocol => udp,
+ dup => false},
+ ok = api_ffd_open_and_info(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) a socket from an already existing
+%% file descriptor (FD) and info of an IPv6 UDP (dgram) socket.
+%% With some extra checks...
+%% IPv6
+%% Without dup
+api_ffd_open_wod_and_info_udp6(suite) ->
+ [];
+api_ffd_open_wod_and_info_udp6(doc) ->
+ [];
+api_ffd_open_wod_and_info_udp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_ffd_open_wod_and_info_udp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet6,
+ type => dgram,
+ protocol => udp,
+ dup => false},
+ ok = api_ffd_open_and_info(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) a socket from an already existing
+%% file descriptor (FD) and info of an IPv4 UDP (dgram) socket.
+%% With some extra checks...
+%% IPv4
+%% With dup
+api_ffd_open_wd_and_info_udp4(suite) ->
+ [];
+api_ffd_open_wd_and_info_udp4(doc) ->
+ [];
+api_ffd_open_wd_and_info_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_ffd_wd_open_and_info_udp4,
+ fun() ->
+ InitState = #{domain => inet,
+ type => dgram,
+ protocol => udp,
+ dup => true},
+ ok = api_ffd_open_and_info(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) a socket from an already existing
+%% file descriptor (FD) and info of an IPv4 UDP (dgram) socket.
+%% With some extra checks...
+%% IPv6
+%% With dup
+api_ffd_open_wd_and_info_udp6(suite) ->
+ [];
+api_ffd_open_wd_and_info_udp6(doc) ->
+ [];
+api_ffd_open_wd_and_info_udp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_ffd_wd_open_and_info_udp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet,
+ type => dgram,
+ protocol => udp,
+ dup => true},
+ ok = api_ffd_open_and_info(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) a socket from an already existing
+%% file descriptor (FD) and info of an IPv4 TCP (stream) socket.
+%% With some extra checks...
+%% IPv6
+%% Without dup
+api_ffd_open_wod_and_info_tcp4(suite) ->
+ [];
+api_ffd_open_wod_and_info_tcp4(doc) ->
+ [];
+api_ffd_open_wod_and_info_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_ffd_open_wod_and_info_tcp4,
+ fun() ->
+ InitState = #{domain => inet,
+ type => stream,
+ protocol => tcp,
+ dup => false},
+ ok = api_ffd_open_and_info(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) a socket from an already existing
+%% file descriptor (FD) and info of an IPv6 TCP (stream) socket.
+%% With some extra checks...
+%% IPv6
+%% Without dup
+api_ffd_open_wod_and_info_tcp6(suite) ->
+ [];
+api_ffd_open_wod_and_info_tcp6(doc) ->
+ [];
+api_ffd_open_wod_and_info_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_ffd_open_wod_and_info_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet6,
+ type => stream,
+ protocol => tcp,
+ dup => false},
+ ok = api_ffd_open_and_info(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) a socket from an already existing
+%% file descriptor (FD) and info of an IPv4 TCP (stream) socket.
+%% With some extra checks...
+%% IPv6
+%% With dup
+api_ffd_open_wd_and_info_tcp4(suite) ->
+ [];
+api_ffd_open_wd_and_info_tcp4(doc) ->
+ [];
+api_ffd_open_wd_and_info_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_ffd_open_wd_and_info_tcp4,
+ fun() ->
+ InitState = #{domain => inet,
+ type => stream,
+ protocol => tcp,
+ dup => true},
+ ok = api_ffd_open_and_info(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) a socket from an already existing
+%% file descriptor (FD) and info of an IPv6 TCP (stream) socket.
+%% With some extra checks...
+%% IPv6
+%% With dup
+api_ffd_open_wd_and_info_tcp6(suite) ->
+ [];
+api_ffd_open_wd_and_info_tcp6(doc) ->
+ [];
+api_ffd_open_wd_and_info_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_ffd_open_wd_and_info_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet6,
+ type => stream,
+ protocol => tcp,
+ dup => true},
+ ok = api_ffd_open_and_info(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_ffd_open_and_info(InitState) ->
+ Seq =
+ [
+ #{desc => "open",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Protocol} = State) ->
+ case socket:open(Domain, Type, Protocol) of
+ {ok, Sock1} ->
+ {ok, State#{sock1 => Sock1}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "get socket (1) FD",
+ cmd => fun(#{sock1 := Sock1} = State) ->
+ case socket:getopt(Sock1, otp, fd) of
+ {ok, FD} ->
+ ?SEV_IPRINT("FD: ~w", [FD]),
+ {ok, State#{fd => FD}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("failed get FD: "
+ "~n ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "check if we need to provide protocol or not",
+ cmd => fun(#{sock1 := Sock1} = State) ->
+ case socket:getopt(Sock1, socket, protocol) of
+ {ok, _} ->
+ ?SEV_IPRINT("protocol accessible"),
+ {ok, State#{provide_protocol => false}};
+ {error, Reason} ->
+ ?SEV_IPRINT("failed get protocol: "
+ "~n ~p", [Reason]),
+ {ok, State#{provide_protocol => true}}
+ end
+ end},
+ #{desc => "open with FD",
+ cmd => fun(#{fd := FD,
+ dup := DUP,
+ provide_protocol := true,
+ protocol := Protocol} = State) ->
+ case socket:open(FD, #{dup => DUP,
+ protocol => Protocol}) of
+ {ok, Sock2} ->
+ ?SEV_IPRINT("socket 2 open"),
+ {ok, State#{sock2 => Sock2}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("failed open socket with FD (~w): "
+ "~n ~p", [FD, Reason]),
+ ERROR
+ end;
+ (#{fd := FD,
+ dup := DUP,
+ provide_protocol := false} = State) ->
+ case socket:open(FD, #{dup => DUP}) of
+ {ok, Sock2} ->
+ ?SEV_IPRINT("socket 2 open"),
+ {ok, State#{sock2 => Sock2}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("failed open socket with FD (~w): "
+ "~n ~p", [FD, Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "get socket (1) info",
+ cmd => fun(#{sock1 := Sock} = State) ->
+ %% socket:setopt(Sock, otp, debug, true),
+ Info = socket:info(Sock),
+ %% socket:setopt(Sock, otp, debug, false),
+ ?SEV_IPRINT("Got Info: "
+ "~n ~p", [Info]),
+ {ok, State#{info1 => Info}}
+ end},
+ #{desc => "get socket (2) info",
+ cmd => fun(#{sock2 := Sock} = State) ->
+ %% socket:setopt(Sock, otp, debug, true),
+ Info = socket:info(Sock),
+ %% socket:setopt(Sock, otp, debug, false),
+ ?SEV_IPRINT("Got Info: "
+ "~n ~p", [Info]),
+ {ok, State#{info2 => Info}}
+ end},
+ #{desc => "validate socket (1) info",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Protocol,
+ info1 := #{domain := Domain,
+ type := Type,
+ protocol := Protocol,
+ ctype := normal,
+ counters := _,
+ num_readers := 0,
+ num_writers := 0,
+ num_acceptors := 0}}) ->
+ ok;
+ (#{domain := Domain,
+ type := Type,
+ protocol := Protocol,
+ info := Info}) ->
+ ?SEV_EPRINT("Unexpected Info for socket 1: "
+ "~n (expected) Domain: ~p"
+ "~n (expected) Type: ~p"
+ "~n (expected) Protocol: ~p"
+ "~n (expected) Create Type: ~p"
+ "~n ~p",
+ [Domain, Type, Protocol, normal, Info]),
+ {error, unexpected_infio}
+ end},
+ #{desc => "validate socket (2) info",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Protocol,
+ fd := _FD,
+ dup := false,
+ info2 := #{domain := Domain,
+ type := Type,
+ protocol := Protocol,
+ ctype := fromfd,
+ counters := _,
+ num_readers := 0,
+ num_writers := 0,
+ num_acceptors := 0}}) ->
+ ok;
+ (#{domain := Domain,
+ type := Type,
+ protocol := Protocol,
+ fd := _FD,
+ dup := false,
+ info := Info}) ->
+ ?SEV_EPRINT("Unexpected Info for socket 2: "
+ "~n (expected) Domain: ~p"
+ "~n (expected) Type: ~p"
+ "~n (expected) Protocol: ~p"
+ "~n (expected) Create Type: ~p"
+ "~n ~p",
+ [Domain, Type, Protocol,
+ fromfd, Info]),
+ {error, unexpected_info};
+ (#{domain := Domain,
+ type := Type,
+ protocol := Protocol,
+ fd := FD,
+ dup := true,
+ info2 := #{domain := Domain,
+ type := Type,
+ protocol := Protocol,
+ ctype := {fromfd, FD},
+ counters := _,
+ num_readers := 0,
+ num_writers := 0,
+ num_acceptors := 0}}) ->
+ ok;
+ (#{domain := Domain,
+ type := Type,
+ protocol := Protocol,
+ fd := FD,
+ dup := true,
+ info := Info}) ->
+ ?SEV_EPRINT("Unexpected Info for socket 2: "
+ "~n (expected) Domain: ~p"
+ "~n (expected) Type: ~p"
+ "~n (expected) Protocol: ~p"
+ "~n (expected) Create Type: ~p"
+ "~n ~p",
+ [Domain, Type, Protocol,
+ {fromfd, FD}, Info]),
+ {error, unexpected_info}
+ end},
+ #{desc => "close socket (1)",
+ cmd => fun(#{sock1 := Sock} = _State) ->
+ socket:close(Sock)
+ end},
+ #{desc => "close socket (2)",
+ cmd => fun(#{sock2 := Sock} = _State) ->
+ socket:close(Sock)
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+ Evaluator = ?SEV_START("tester", Seq, InitState),
+ ok = ?SEV_AWAIT_FINISH([Evaluator]).
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open a socket (1) and then create another socket (2) from
+%% its file descriptor *without* dup.
+%% Exchange som data from via both "client" sockets.
+%% Finally close the second socket. Ensure that the original socket
+%% has not been closed (test by sending some data).
+%% IPv4 UDP (dgram) socket.
+%%
+%% <WARNING>
+%%
+%% This is *not* how its intended to be used.
+%% That an erlang process creating a socket and then handing over the
+%% file descriptor to another erlang process. *But* its a convient way
+%% to test it!
+%%
+%% </WARNING>
+%%
+api_ffd_open_and_open_wod_and_send_udp4(suite) ->
+ [];
+api_ffd_open_and_open_wod_and_send_udp4(doc) ->
+ [];
+api_ffd_open_and_open_wod_and_send_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_ffd_open_and_open_wod_and_send_udp4,
+ fun() ->
+ InitState = #{domain => inet,
+ type => dgram,
+ protocol => udp,
+ dup => false},
+ ok = api_ffd_open_and_open_and_send_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open a socket (1) and then create another socket (2) from
+%% its file descriptor *without* dup.
+%% Exchange som data from via both "client" sockets.
+%% Finally close the second socket. Ensure that the original socket
+%% has not been closed (test by sending some data).
+%% IPv6 UDP (dgram) socket.
+%%
+%% <WARNING>
+%%
+%% This is *not* how its intended to be used.
+%% That an erlang process creating a socket and then handing over the
+%% file descriptor to another erlang process. *But* its a convient way
+%% to test it!
+%%
+%% </WARNING>
+%%
+api_ffd_open_and_open_wod_and_send_udp6(suite) ->
+ [];
+api_ffd_open_and_open_wod_and_send_udp6(doc) ->
+ [];
+api_ffd_open_and_open_wod_and_send_udp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_ffd_open_and_open_wod_and_send_udp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet6,
+ type => dgram,
+ protocol => udp,
+ dup => false},
+ ok = api_ffd_open_and_open_and_send_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open a socket (1) and then create another socket (2) from
+%% its file descriptor *with* dup.
+%% Exchange som data from via both "client" sockets.
+%% Finally close the second socket. Ensure that the original socket
+%% has not been closed (test by sending some data).
+%% IPv4 UDP (dgram) socket.
+%%
+api_ffd_open_and_open_wd_and_send_udp4(suite) ->
+ [];
+api_ffd_open_and_open_wd_and_send_udp4(doc) ->
+ [];
+api_ffd_open_and_open_wd_and_send_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_ffd_open_and_open_wd_and_send_udp4,
+ fun() ->
+ InitState = #{domain => inet,
+ type => dgram,
+ protocol => udp,
+ dup => true},
+ ok = api_ffd_open_and_open_and_send_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open a socket (1) and then create another socket (2) from
+%% its file descriptor *with* dup.
+%% Exchange som data from via both "client" sockets.
+%% Finally close the second socket. Ensure that the original socket
+%% has not been closed (test by sending some data).
+%% IPv6 UDP (dgram) socket.
+%%
+api_ffd_open_and_open_wd_and_send_udp6(suite) ->
+ [];
+api_ffd_open_and_open_wd_and_send_udp6(doc) ->
+ [];
+api_ffd_open_and_open_wd_and_send_udp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_ffd_open_and_open_wd_and_send_udp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet6,
+ type => dgram,
+ protocol => udp,
+ dup => true},
+ ok = api_ffd_open_and_open_and_send_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_ffd_open_and_open_and_send_udp(InitState) ->
+ Send = fun(Sock, Data, Dest) ->
+ socket:sendto(Sock, Data, Dest)
+ end,
+ Recv = fun(Sock) ->
+ socket:recvfrom(Sock)
+ end,
+ api_ffd_open_and_open_and_send_udp2(InitState#{send => Send,
+ recv => Recv}).
+
+api_ffd_open_and_open_and_send_udp2(InitState) ->
+ process_flag(trap_exit, true),
+ ServerSeq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa => LSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, dgram, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{sock := Sock, lsa := LSA} = State) ->
+ case sock_bind(Sock, LSA) of
+ {ok, Port} ->
+ ?SEV_IPRINT("bound to port: ~w", [Port]),
+ {ok, State#{port => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, port := Port}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Port),
+ ok
+ end},
+
+ #{desc => "await request 1 (recv)",
+ cmd => fun(#{sock := Sock, recv := Recv} = State) ->
+ case Recv(Sock) of
+ {ok, {Source, ?BASIC_REQ}} ->
+ ?SEV_IPRINT("received request (1) from: "
+ "~n ~p", [Source]),
+ {ok, State#{source => Source}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready 1 (recv request)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_req),
+ ok
+ end},
+ #{desc => "await continue 1 (with send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply)
+ end},
+ #{desc => "send reply 1",
+ cmd => fun(#{sock := Sock, send := Send, source := Source}) ->
+ Send(Sock, ?BASIC_REP, Source)
+ end},
+ #{desc => "announce ready 1 (send reply)",
+ cmd => fun(#{tester := Tester} = State) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_reply),
+ {ok, maps:remove(source, State)}
+ end},
+
+ #{desc => "await request 2 (recv)",
+ cmd => fun(#{sock := Sock, recv := Recv} = State) ->
+ case Recv(Sock) of
+ {ok, {Source, ?BASIC_REQ}} ->
+ ?SEV_IPRINT("received request (2) from: "
+ "~n ~p", [Source]),
+ {ok, State#{source => Source}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready 2 (recv request)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_req),
+ ok
+ end},
+ #{desc => "await continue 2 (with send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply)
+ end},
+ #{desc => "send reply 2",
+ cmd => fun(#{sock := Sock, send := Send, source := Source}) ->
+ Send(Sock, ?BASIC_REP, Source)
+ end},
+ #{desc => "announce ready 2 (send reply)",
+ cmd => fun(#{tester := Tester} = State) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_reply),
+ {ok, maps:remove(source, State)}
+ end},
+
+ #{desc => "await request 3 (recv)",
+ cmd => fun(#{sock := Sock, recv := Recv} = State) ->
+ case Recv(Sock) of
+ {ok, {Source, ?BASIC_REQ}} ->
+ ?SEV_IPRINT("received request (2) from: "
+ "~n ~p", [Source]),
+ {ok, State#{source => Source}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready 3 (recv request)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_req),
+ ok
+ end},
+ #{desc => "await continue 3 (with send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply)
+ end},
+ #{desc => "send reply 3",
+ cmd => fun(#{sock := Sock, send := Send, source := Source}) ->
+ Send(Sock, ?BASIC_REP, Source)
+ end},
+ #{desc => "announce ready 3 (send reply)",
+ cmd => fun(#{tester := Tester} = State) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_reply),
+ {ok, maps:remove(source, State)}
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ Client1Seq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ {Tester, Port} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, server_port => Port}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** The init part ***
+ #{desc => "which server (local) address",
+ cmd => fun(#{domain := Domain, server_port := Port} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ SSA = LSA#{port => Port},
+ {ok, State#{local_sa => LSA, server_sa => SSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, dgram, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{sock := Sock, local_sa := LSA} = _State) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "get socket FD",
+ cmd => fun(#{sock := Sock} = State) ->
+ case socket:getopt(Sock, otp, fd) of
+ {ok, FD} ->
+ ?SEV_IPRINT("FD: ~w", [FD]),
+ {ok, State#{fd => FD}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("failed get FD: "
+ "~n ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester,
+ fd := FD}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, FD),
+ ok
+ end},
+
+ %% *** The actual test ***
+ #{desc => "await continue (send request 1)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_req)
+ end},
+ #{desc => "send request 1 (to server)",
+ cmd => fun(#{sock := Sock, send := Send, server_sa := SSA}) ->
+ Send(Sock, ?BASIC_REQ, SSA)
+ end},
+ #{desc => "announce ready (send request 1)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_req),
+ ok
+ end},
+ #{desc => "await recv reply 1 (from server)",
+ cmd => fun(#{sock := Sock, recv := Recv}) ->
+ {ok, {_, ?BASIC_REP}} = Recv(Sock),
+ ok
+ end},
+ #{desc => "announce ready (recv reply 1)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_reply),
+ ok
+ end},
+
+ #{desc => "await continue (send request 3)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_req)
+ end},
+ #{desc => "send request 3 (to server)",
+ cmd => fun(#{sock := Sock, send := Send, server_sa := SSA}) ->
+ Send(Sock, ?BASIC_REQ, SSA)
+ end},
+ #{desc => "announce ready (send request 3)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_req),
+ ok
+ end},
+ #{desc => "await recv reply 3 (from server)",
+ cmd => fun(#{sock := Sock, recv := Recv}) ->
+ {ok, {_, ?BASIC_REP}} = Recv(Sock),
+ ok
+ end},
+ #{desc => "announce ready (recv reply 3)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_reply),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ Client2Seq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ {Tester, {Port, FD}} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ server_port => Port,
+ fd => FD}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** The init part ***
+ #{desc => "which server (local) address",
+ cmd => fun(#{domain := Domain, server_port := Port} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ SSA = LSA#{port => Port},
+ {ok, State#{server_sa => SSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{fd := FD,
+ dup := DUP} = State) ->
+ case socket:open(FD, #{dup => DUP}) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% *** The actual test ***
+ #{desc => "await continue (send request 2)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_req)
+ end},
+ #{desc => "send request 2 (to server)",
+ cmd => fun(#{sock := Sock, send := Send, server_sa := SSA}) ->
+ Send(Sock, ?BASIC_REQ, SSA)
+ end},
+ #{desc => "announce ready (send request 2)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_req),
+ ok
+ end},
+ #{desc => "await recv reply 2 (from server)",
+ cmd => fun(#{sock := Sock, recv := Recv}) ->
+ {ok, {_, ?BASIC_REP}} = Recv(Sock),
+ ok
+ end},
+ #{desc => "announce ready (recv reply 2)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_reply),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client 1",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client 2",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = State) ->
+ {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{server_port => Port}}
+ end},
+
+ %% Start the client 1
+ #{desc => "order client 1 start",
+ cmd => fun(#{client1 := Pid, server_port := Port} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Port),
+ ok
+ end},
+ #{desc => "await client 1 ready (init)",
+ cmd => fun(#{client1 := Pid} = State) ->
+ case ?SEV_AWAIT_READY(Pid, client1, init) of
+ {ok, FD} ->
+ {ok, State#{fd => FD}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Client 1 init error: "
+ "~n ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ %% Start the client 2
+ #{desc => "order client 2 start",
+ cmd => fun(#{client2 := Pid,
+ server_port := Port,
+ fd := FD} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, {Port, FD}),
+ ok
+ end},
+ #{desc => "await client 2 ready (init)",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client2, init)
+ end},
+
+ %% *** The actual test ***
+
+ #{desc => "order client 1 to continue (with send request 1)",
+ cmd => fun(#{client1 := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send_req),
+ ok
+ end},
+ #{desc => "await client 1 ready (with send request 1)",
+ cmd => fun(#{client1 := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client1, send_req)
+ end},
+ #{desc => "await server ready (request recv 1)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv_req)
+ end},
+ #{desc => "order server to continue (with send reply 1)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, send_reply),
+ ok
+ end},
+ #{desc => "await server ready (with reply 1 sent)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, send_reply)
+ end},
+ #{desc => "await client 1 ready (reply recv 1)",
+ cmd => fun(#{client1 := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client1, recv_reply)
+ end},
+
+
+ #{desc => "order client 2 to continue (with send request 2)",
+ cmd => fun(#{client2 := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send_req),
+ ok
+ end},
+ #{desc => "await client 2 ready (with send request 2)",
+ cmd => fun(#{client2 := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client2, send_req)
+ end},
+ #{desc => "await server ready (request recv 2)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv_req)
+ end},
+ #{desc => "order server to continue (with send reply 2)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, send_reply),
+ ok
+ end},
+ #{desc => "await server ready (with reply 2 sent)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, send_reply)
+ end},
+ #{desc => "await client 2 ready (reply recv 2)",
+ cmd => fun(#{client2 := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client2, recv_reply)
+ end},
+
+
+ #{desc => "order client 2 to terminate",
+ cmd => fun(#{client2 := Client} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await client 2 termination",
+ cmd => fun(#{client2 := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(client2, State),
+ {ok, State1}
+ end},
+
+
+ #{desc => "order client 1 to continue (with send request 3)",
+ cmd => fun(#{client1 := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send_req),
+ ok
+ end},
+ #{desc => "await client 1 ready (with send request 3)",
+ cmd => fun(#{client1 := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client1, send_req)
+ end},
+ #{desc => "await server ready (request recv 3)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv_req)
+ end},
+ #{desc => "order server to continue (with send reply 3)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, send_reply),
+ ok
+ end},
+ #{desc => "await server ready (with reply 3 sent)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, send_reply)
+ end},
+ #{desc => "await client 1 ready (reply recv 3)",
+ cmd => fun(#{client1 := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client1, recv_reply)
+ end},
+
+
+ %% *** Termination ***
+ #{desc => "order client 1 to terminate",
+ cmd => fun(#{client1 := Client} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await client 1 termination",
+ cmd => fun(#{client1 := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(client1, State),
+ {ok, State1}
+ end},
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Server),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Server} = State) ->
+ ?SEV_AWAIT_TERMINATION(Server),
+ State1 = maps:remove(server, State),
+ {ok, State1}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start server evaluator"),
+ Server = ?SEV_START("server", ServerSeq, maps:remove(dup, InitState)),
+
+ i("start (socket origin) client 1 evaluator"),
+ Client1 = ?SEV_START("client-1", Client1Seq, maps:remove(dup, InitState)),
+ i("await evaluator(s)"),
+
+ i("start client 2 evaluator"),
+ Client2 = ?SEV_START("client-2", Client2Seq, InitState),
+ i("await evaluator(s)"),
+
+ i("start tester evaluator"),
+ TesterInitState = #{server => Server#ev.pid,
+ client1 => Client1#ev.pid,
+ client2 => Client2#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ ok = ?SEV_AWAIT_FINISH([Server, Client1, Client2, Tester]).
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open a socket (1), connect to a server and then create
+%% another socket (2) from its file descriptor *without* dup.
+%% Exchange som data from via both "client" sockets.
+%% Finally close the second socket. Ensure that the original socket
+%% has not been closed (test by sending some data).
+%% IPv4 TCP (stream) socket.
+%%
+%% <WARNING>
+%%
+%% This is *not* how its intended to be used.
+%% That an erlang process creating a socket and then handing over the
+%% file descriptor to another erlang process. *But* its a convient way
+%% to test it!
+%%
+%% </WARNING>
+%%
+api_ffd_open_connect_and_open_wod_and_send_tcp4(suite) ->
+ [];
+api_ffd_open_connect_and_open_wod_and_send_tcp4(doc) ->
+ [];
+api_ffd_open_connect_and_open_wod_and_send_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_ffd_open_connect_and_open_wod_and_send_tcp4,
+ fun() ->
+ InitState = #{domain => inet,
+ type => stream,
+ protocol => tcp,
+ dup => false},
+ ok = api_ffd_open_connect_and_open_and_send_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open a socket (1), connect to a server and then create
+%% another socket (2) from its file descriptor *without* dup.
+%% Exchange som data from via both "client" sockets.
+%% Finally close the second socket. Ensure that the original socket
+%% has not been closed (test by sending some data).
+%% IPv6 TCP (stream) socket.
+%%
+%% <WARNING>
+%%
+%% This is *not* how its intended to be used.
+%% That an erlang process creating a socket and then handing over the
+%% file descriptor to another erlang process. *But* its a convient way
+%% to test it!
+%%
+%% </WARNING>
+%%
+api_ffd_open_connect_and_open_wod_and_send_tcp6(suite) ->
+ [];
+api_ffd_open_connect_and_open_wod_and_send_tcp6(doc) ->
+ [];
+api_ffd_open_connect_and_open_wod_and_send_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_ffd_open_connect_and_open_wod_and_send_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet6,
+ type => stream,
+ protocol => tcp,
+ dup => false},
+ ok = api_ffd_open_connect_and_open_and_send_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open a socket (1), connect to a server and then create
+%% another socket (2) from its file descriptor *with* dup.
+%% Exchange som data from via both "client" sockets.
+%% Finally close the second socket. Ensure that the original socket
+%% has not been closed (test by sending some data).
+%% IPv4 TCP (stream) socket.
+api_ffd_open_connect_and_open_wd_and_send_tcp4(suite) ->
+ [];
+api_ffd_open_connect_and_open_wd_and_send_tcp4(doc) ->
+ [];
+api_ffd_open_connect_and_open_wd_and_send_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_ffd_open_connect_and_open_wd_and_send_tcp4,
+ fun() ->
+ InitState = #{domain => inet,
+ type => stream,
+ protocol => tcp,
+ dup => true},
+ ok = api_ffd_open_connect_and_open_and_send_tcp(InitState)
+ end).
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open a socket (1), connect to a server and then create
+%% another socket (2) from its file descriptor *with* dup.
+%% Exchange som data from via both "client" sockets.
+%% Finally close the second socket. Ensure that the original socket
+%% has not been closed (test by sending some data).
+%% IPv6 TCP (stream) socket.
+api_ffd_open_connect_and_open_wd_and_send_tcp6(suite) ->
+ [];
+api_ffd_open_connect_and_open_wd_and_send_tcp6(doc) ->
+ [];
+api_ffd_open_connect_and_open_wd_and_send_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_ffd_open_connect_and_open_wd_and_send_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet6,
+ type => stream,
+ protocol => tcp,
+ dup => true},
+ ok = api_ffd_open_connect_and_open_and_send_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_ffd_open_connect_and_open_and_send_tcp(InitState) ->
+ Send = fun(Sock, Data) ->
+ socket:send(Sock, Data)
+ end,
+ Recv = fun(Sock) ->
+ socket:recv(Sock)
+ end,
+ api_ffd_open_connect_and_open_and_send_tcp2(InitState#{send => Send,
+ recv => Recv}).
+
+api_ffd_open_connect_and_open_and_send_tcp2(InitState) ->
+ process_flag(trap_exit, true),
+ ServerSeq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa => LSA}}
+ end},
+ #{desc => "create listen socket",
+ cmd => fun(#{domain := Domain,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, stream, Proto) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{lsock := LSock, lsa := LSA} = State) ->
+ case sock_bind(LSock, LSA) of
+ {ok, Port} ->
+ ?SEV_IPRINT("bound to port: ~w", [Port]),
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, lport := Port}) ->
+ %% This is actually not used for unix domain socket
+ ?SEV_ANNOUNCE_READY(Tester, init, Port),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "await connection",
+ cmd => fun(#{lsock := LSock} = State) ->
+ case socket:accept(LSock) of
+ {ok, Sock} ->
+ ?SEV_IPRINT("accepted: ~n ~p", [Sock]),
+ {ok, State#{csock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept),
+ ok
+ end},
+
+ #{desc => "await request 1 (recv)",
+ cmd => fun(#{csock := Sock, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, ?BASIC_REQ} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready 1 (recv request)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_req),
+ ok
+ end},
+ #{desc => "await continue 1 (with send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply)
+ end},
+ #{desc => "send reply 1",
+ cmd => fun(#{csock := Sock, send := Send}) ->
+ Send(Sock, ?BASIC_REP)
+ end},
+ #{desc => "announce ready 1 (send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_reply),
+ ok
+ end},
+
+ #{desc => "await request 2 (recv)",
+ cmd => fun(#{csock := Sock, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, ?BASIC_REQ} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready 2 (recv request)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_req),
+ ok
+ end},
+ #{desc => "await continue 2 (with send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply)
+ end},
+ #{desc => "send reply 2",
+ cmd => fun(#{csock := Sock, send := Send}) ->
+ Send(Sock, ?BASIC_REP)
+ end},
+ #{desc => "announce ready 2 (send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_reply),
+ ok
+ end},
+
+ #{desc => "await request 3 (recv)",
+ cmd => fun(#{csock := Sock, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, ?BASIC_REQ} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready 3 (recv request)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_req),
+ ok
+ end},
+ #{desc => "await continue 3 (with send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply)
+ end},
+ #{desc => "send reply 3",
+ cmd => fun(#{csock := Sock, send := Send}) ->
+ Send(Sock, ?BASIC_REP)
+ end},
+ #{desc => "announce ready 3 (send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_reply),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close connection socket",
+ cmd => fun(#{csock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(csock, State)}
+ end},
+ #{desc => "close listen socket",
+ cmd => fun(#{lsock := LSock} = State) ->
+ case socket:close(LSock) of
+ ok ->
+ {ok, maps:remove(lsock, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ Client1Seq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ {Tester, Port} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, server_port => Port}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** The init part ***
+ #{desc => "which server (local) address",
+ cmd => fun(#{domain := Domain, server_port := Port} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ SSA = LSA#{port => Port},
+ {ok, State#{local_sa => LSA, server_sa => SSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, stream, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{sock := Sock, local_sa := LSA} = _State) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+
+ #{desc => "await continue (connect)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, connect)
+ end},
+ #{desc => "connect to server",
+ cmd => fun(#{sock := Sock, server_sa := SSA}) ->
+ socket:connect(Sock, SSA)
+ end},
+ #{desc => "get socket FD",
+ cmd => fun(#{sock := Sock} = State) ->
+ case socket:getopt(Sock, otp, fd) of
+ {ok, FD} ->
+ ?SEV_IPRINT("FD: ~w", [FD]),
+ {ok, State#{fd => FD}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("failed get FD: "
+ "~n ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (connect)",
+ cmd => fun(#{tester := Tester,
+ fd := FD}) ->
+ ?SEV_ANNOUNCE_READY(Tester, connect, FD),
+ ok
+ end},
+
+
+ %% *** The actual test ***
+ #{desc => "await continue (send request 1)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_req)
+ end},
+ #{desc => "send request 1 (to server)",
+ cmd => fun(#{sock := Sock, send := Send}) ->
+ Send(Sock, ?BASIC_REQ)
+ end},
+ #{desc => "announce ready (send request 1)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_req),
+ ok
+ end},
+ #{desc => "await recv reply 1 (from server)",
+ cmd => fun(#{sock := Sock, recv := Recv}) ->
+ {ok, ?BASIC_REP} = Recv(Sock),
+ ok
+ end},
+ #{desc => "announce ready (recv reply 1)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_reply),
+ ok
+ end},
+
+ #{desc => "await continue (send request 3)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_req)
+ end},
+ #{desc => "send request 3 (to server)",
+ cmd => fun(#{sock := Sock, send := Send}) ->
+ Send(Sock, ?BASIC_REQ)
+ end},
+ #{desc => "announce ready (send request 3)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_req),
+ ok
+ end},
+ #{desc => "await recv reply 3 (from server)",
+ cmd => fun(#{sock := Sock, recv := Recv}) ->
+ {ok, ?BASIC_REP} = Recv(Sock),
+ ok
+ end},
+ #{desc => "announce ready (recv reply 3)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_reply),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ Client2Seq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ {Tester, FD} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, fd => FD}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** The init part ***
+ #{desc => "create socket",
+ cmd => fun(#{fd := FD,
+ dup := DUP} = State) ->
+ case socket:open(FD, #{dup => DUP}) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% *** The actual test ***
+ #{desc => "await continue (send request 2)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_req)
+ end},
+ #{desc => "send request 2 (to server)",
+ cmd => fun(#{sock := Sock, send := Send}) ->
+ Send(Sock, ?BASIC_REQ)
+ end},
+ #{desc => "announce ready (send request 2)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_req),
+ ok
+ end},
+ #{desc => "await recv reply 2 (from server)",
+ cmd => fun(#{sock := Sock, recv := Recv}) ->
+ {ok, ?BASIC_REP} = Recv(Sock),
+ ok
+ end},
+ #{desc => "announce ready (recv reply 2)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_reply),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client 1",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client 2",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = State) ->
+ {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{server_port => Port}}
+ end},
+
+ %% Start the client 1
+ #{desc => "order client 1 start",
+ cmd => fun(#{client1 := Pid, server_port := Port} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Port),
+ ok
+ end},
+ #{desc => "await client 1 ready (init)",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client1, init)
+ end},
+
+ #{desc => "order server to continue (with accept)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, accept),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order client 1 to continue (with connect)",
+ cmd => fun(#{client1 := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, connect),
+ ok
+ end},
+ #{desc => "await client 1 ready (connect)",
+ cmd => fun(#{client1 := Pid} = State) ->
+ {ok, FD} = ?SEV_AWAIT_READY(Pid, client1, connect),
+ {ok, State#{fd => FD}}
+ end},
+ #{desc => "await server ready (accept)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, accept)
+ end},
+
+ %% Start the client 2
+ #{desc => "order client 2 start",
+ cmd => fun(#{client2 := Pid, fd := FD} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, FD),
+ ok
+ end},
+ #{desc => "await client 2 ready (init)",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client2, init)
+ end},
+
+ %% *** The actual test ***
+
+ #{desc => "order client 1 to continue (with send request 1)",
+ cmd => fun(#{client1 := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send_req),
+ ok
+ end},
+ #{desc => "await client 1 ready (with send request 1)",
+ cmd => fun(#{client1 := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client1, send_req)
+ end},
+ #{desc => "await server ready (request recv 1)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv_req)
+ end},
+ #{desc => "order server to continue (with send reply 1)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, send_reply),
+ ok
+ end},
+ #{desc => "await server ready (with reply 1 sent)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, send_reply)
+ end},
+ #{desc => "await client 1 ready (reply recv 1)",
+ cmd => fun(#{client1 := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client1, recv_reply)
+ end},
+
+
+ #{desc => "order client 2 to continue (with send request 2)",
+ cmd => fun(#{client2 := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send_req),
+ ok
+ end},
+ #{desc => "await client 2 ready (with send request 2)",
+ cmd => fun(#{client2 := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client2, send_req)
+ end},
+ #{desc => "await server ready (request recv 2)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv_req)
+ end},
+ #{desc => "order server to continue (with send reply 2)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, send_reply),
+ ok
+ end},
+ #{desc => "await server ready (with reply 2 sent)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, send_reply)
+ end},
+ #{desc => "await client 2 ready (reply recv 2)",
+ cmd => fun(#{client2 := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client2, recv_reply)
+ end},
+
+
+ #{desc => "order client 2 to terminate",
+ cmd => fun(#{client2 := Client} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await client 2 termination",
+ cmd => fun(#{client2 := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(client2, State),
+ {ok, State1}
+ end},
+
+
+ #{desc => "order client 1 to continue (with send request 3)",
+ cmd => fun(#{client1 := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send_req),
+ ok
+ end},
+ #{desc => "await client 1 ready (with send request 3)",
+ cmd => fun(#{client1 := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client1, send_req)
+ end},
+ #{desc => "await server ready (request recv 3)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv_req)
+ end},
+ #{desc => "order server to continue (with send reply 3)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, send_reply),
+ ok
+ end},
+ #{desc => "await server ready (with reply 3 sent)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, send_reply)
+ end},
+ #{desc => "await client 1 ready (reply recv 3)",
+ cmd => fun(#{client1 := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client1, recv_reply)
+ end},
+
+
+ %% *** Termination ***
+ #{desc => "order client 1 to terminate",
+ cmd => fun(#{client1 := Client} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await client 1 termination",
+ cmd => fun(#{client1 := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(client1, State),
+ {ok, State1}
+ end},
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Server),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Server} = State) ->
+ ?SEV_AWAIT_TERMINATION(Server),
+ State1 = maps:remove(server, State),
+ {ok, State1}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start server evaluator"),
+ Server = ?SEV_START("server", ServerSeq, maps:remove(dup, InitState)),
+
+ i("start (socket origin) client 1 evaluator"),
+ Client1 = ?SEV_START("client-1", Client1Seq, maps:remove(dup, InitState)),
+ i("await evaluator(s)"),
+
+ i("start client 2 evaluator"),
+ Client2 = ?SEV_START("client-2", Client2Seq, InitState),
+ i("await evaluator(s)"),
+
+ i("start tester evaluator"),
+ TesterInitState = #{server => Server#ev.pid,
+ client1 => Client1#ev.pid,
+ client2 => Client2#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ ok = ?SEV_AWAIT_FINISH([Server, Client1, Client2, Tester]).
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% %%
+%% API ASYNC %%
+%% %%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically establish a TCP connection via an async connect. IPv4.
+
+api_a_connect_tcp4(suite) ->
+ [];
+api_a_connect_tcp4(doc) ->
+ [];
+api_a_connect_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_a_connect_tcp4,
+ fun() ->
+ ok = api_a_connect_tcpD(inet)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically establish a TCP connection via an async connect. IPv6.
+
+api_a_connect_tcp6(suite) ->
+ [];
+api_a_connect_tcp6(doc) ->
+ [];
+api_a_connect_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_a_connect_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ ok = api_a_connect_tcpD(inet6)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_a_connect_tcpD(Domain) ->
+ Connect = fun(Sock, SockAddr) ->
+ socket:connect(Sock, SockAddr, nowait)
+ end,
+ Send = fun(Sock, Data) ->
+ socket:send(Sock, Data)
+ end,
+ Recv = fun(Sock) ->
+ socket:recv(Sock)
+ end,
+ InitState = #{domain => Domain,
+ connect => Connect,
+ send => Send,
+ recv => Recv},
+ api_a_connect_tcp(InitState).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_a_connect_tcp(InitState) ->
+ process_flag(trap_exit, true),
+ ServerSeq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa => LSA}}
+ end},
+ #{desc => "create listen socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{lsock := LSock, lsa := LSA} = State) ->
+ case sock_bind(LSock, LSA) of
+ {ok, Port} ->
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, lport := Port}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Port),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "await connection",
+ cmd => fun(#{lsock := LSock} = State) ->
+ case socket:accept(LSock) of
+ {ok, Sock} ->
+ ?SEV_IPRINT("accepted: ~n ~p", [Sock]),
+ {ok, State#{csock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept),
+ ok
+ end},
+
+ #{desc => "await continue (recv_req)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv_req)
+ end},
+ #{desc => "recv req",
+ cmd => fun(#{csock := Sock, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, ?BASIC_REQ} ->
+ ok;
+ {ok, UnexpData} ->
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv_req)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_req),
+ ok
+ end},
+ #{desc => "await continue (send_rep)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_rep)
+ end},
+ #{desc => "send rep",
+ cmd => fun(#{csock := Sock, send := Send}) ->
+ Send(Sock, ?BASIC_REP)
+ end},
+ #{desc => "announce ready (send_rep)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_rep),
+ ok
+ end},
+
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close connection socket",
+ cmd => fun(#{csock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(csock, State)}
+ end},
+ #{desc => "close listen socket",
+ cmd => fun(#{lsock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(lsock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ ClientSeq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ {Tester, Port} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, server_port => Port}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** The init part ***
+ #{desc => "which server (local) address",
+ cmd => fun(#{domain := Domain, server_port := Port} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ SSA = LSA#{port => Port},
+ {ok, State#{local_sa => LSA, server_sa => SSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{sock := Sock, local_sa := LSA} = _State) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% *** The actual test ***
+ #{desc => "await continue (async connect)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, async_connect)
+ end},
+ #{desc => "connect (async) to server",
+ cmd => fun(#{sock := Sock,
+ server_sa := SSA,
+ connect := Connect} = State) ->
+ case Connect(Sock, SSA) of
+ ok ->
+ ?SEV_IPRINT("ok -> "
+ "unexpected success => SKIP",
+ []),
+ {skip, unexpected_success};
+ {select, {select_info, ST, SR}} ->
+ ?SEV_IPRINT("select ->"
+ "~n tag: ~p"
+ "~n ref: ~p", [ST, SR]),
+ {ok, State#{connect_stag => ST,
+ connect_sref => SR}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (connect select)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, connect_select),
+ ok
+ end},
+ #{desc => "await select message",
+ cmd => fun(#{sock := Sock, connect_sref := Ref}) ->
+ receive
+ {'$socket', Sock, select, Ref} ->
+ ?SEV_IPRINT("select message ->"
+ "~n ref: ~p", [Ref]),
+ ok
+ after 5000 ->
+ ?SEV_EPRINT("timeout: "
+ "~n message queue: ~p",
+ [mq()]),
+ {error, timeout}
+ end
+ end},
+ #{desc => "announce ready (select)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, select),
+ ok
+ end},
+ #{desc => "connect (async) to server",
+ cmd => fun(#{sock := Sock, server_sa := SSA, connect := Connect}) ->
+ case Connect(Sock, SSA) of
+ ok ->
+ ok;
+ {select, SelectInfo} ->
+ {error, {unexpected_select, SelectInfo}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (connect)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, connect),
+ ok
+ end},
+ #{desc => "get peername",
+ cmd => fun(#{sock := Sock} = _State) ->
+ case socket:peername(Sock) of
+ {ok, SockAddr} ->
+ ?SEV_IPRINT("Peer Name: ~p", [SockAddr]),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "await continue (send_req)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_req)
+ end},
+ #{desc => "send req",
+ cmd => fun(#{sock := Sock, send := Send}) ->
+ Send(Sock, ?BASIC_REQ)
+ end},
+ #{desc => "announce ready (send_req)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_req),
+ ok
+ end},
+ #{desc => "await continue (recv_rep)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv_rep)
+ end},
+ #{desc => "recv rep",
+ cmd => fun(#{sock := Sock, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, ?BASIC_REP} ->
+ ok;
+ {ok, UnexpData} ->
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv_rep)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_rep),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ ok = socket:close(Sock),
+ State2 = maps:remove(sock, State),
+ State3 = maps:remove(connect_stag, State2),
+ State4 = maps:remove(connect_sref, State3),
+ {ok, State4}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = State) ->
+ {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{server_port => Port}}
+ end},
+
+ %% Start the client
+ #{desc => "order client start",
+ cmd => fun(#{client := Pid, server_port := Port} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Port),
+ ok
+ end},
+ #{desc => "await client ready (init)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client, init)
+ end},
+
+
+ %% *** The actual test ***
+ #{desc => "order client to continue (async connect)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, async_connect),
+ ok
+ end},
+ #{desc => "await client ready (connect select)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, connect_select)
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "order server to continue (with accept)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, accept),
+ ok
+ end},
+ #{desc => "await client ready (select)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, select)
+ end},
+ #{desc => "await client ready (connect)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, connect)
+ end},
+ #{desc => "await server ready (accept)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, accept)
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "order server to recv test req (recv req)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, recv_req),
+ ok
+ end},
+ #{desc => "order client to send test req (send req)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send_req),
+ ok
+ end},
+ #{desc => "await client ready (send_req)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, send_req)
+ end},
+ #{desc => "await server ready (recv_req)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv_req)
+ end},
+ #{desc => "order client to recv test rep (send rep)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, recv_rep),
+ ok
+ end},
+ #{desc => "order server to send test rep (send rep)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, send_rep),
+ ok
+ end},
+ #{desc => "await server ready (send_rep)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, send_rep)
+ end},
+ #{desc => "await client ready (recv_rep)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, recv_rep)
+ end},
+
+
+ %% *** Termination ***
+ #{desc => "order client to terminate",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await client termination",
+ cmd => fun(#{client := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(client, State),
+ {ok, State1}
+ end},
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Server),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Server} = State) ->
+ ?SEV_AWAIT_TERMINATION(Server),
+ State1 = maps:remove(server, State),
+ {ok, State1}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start server evaluator"),
+ Server = ?SEV_START("server", ServerSeq, InitState),
+
+ i("start client evaluator"),
+ Client = ?SEV_START("client", ClientSeq, InitState),
+ i("await evaluator(s)"),
+
+ i("start tester evaluator"),
+ TesterInitState = #{server => Server#ev.pid,
+ client => Client#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]).
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically send and receive on an IPv4 UDP (dgram) socket using
+%% sendto and recvfrom. But we try to be async. That is, we use
+%% the 'nowait' value for the Timeout argument (and await the eventual
+%% select message). Note that we only do this for the recvfrom,
+%% since its much more difficult to "arrange" for sendto.
+%%
+api_a_sendto_and_recvfrom_udp4(suite) ->
+ [];
+api_a_sendto_and_recvfrom_udp4(doc) ->
+ [];
+api_a_sendto_and_recvfrom_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_a_sendto_and_recvfrom_udp4,
+ fun() ->
+ Send = fun(Sock, Data, Dest) ->
+ socket:sendto(Sock, Data, Dest)
+ end,
+ Recv = fun(Sock) ->
+ socket:recvfrom(Sock, 0, nowait)
+ end,
+ InitState = #{domain => inet,
+ send => Send,
+ recv => Recv},
+ ok = api_a_send_and_recv_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically send and receive on an IPv6 UDP (dgram) socket using
+%% sendto and recvfrom. But we try to be async. That is, we use
+%% the 'nowait' value for the Timeout argument (and await the eventual
+%% select message). Note that we only do this for the recvfrom,
+%% since its much more difficult to "arrange" for sendto.
+%%
+api_a_sendto_and_recvfrom_udp6(suite) ->
+ [];
+api_a_sendto_and_recvfrom_udp6(doc) ->
+ [];
+api_a_sendto_and_recvfrom_udp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_a_sendto_and_recvfrom_udp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ Send = fun(Sock, Data, Dest) ->
+ socket:sendto(Sock, Data, Dest)
+ end,
+ Recv = fun(Sock) ->
+ socket:recvfrom(Sock, 0, nowait)
+ end,
+ InitState = #{domain => inet6,
+ send => Send,
+ recv => Recv},
+ ok = api_a_send_and_recv_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically send and receive on an IPv4 UDP (dgram) socket using
+%% sendto and recvfrom. But we try to be async. That is, we use
+%% the 'nowait' value for the Timeout argument (and await the eventual
+%% select message). Note that we only do this for the recvmsg,
+%% since its much more difficult to "arrange" for sendmsg.
+%%
+api_a_sendmsg_and_recvmsg_udp4(suite) ->
+ [];
+api_a_sendmsg_and_recvmsg_udp4(doc) ->
+ [];
+api_a_sendmsg_and_recvmsg_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_a_sendmsg_and_recvmsg_udp4,
+ fun() ->
+ Send = fun(Sock, Data, Dest) ->
+ MsgHdr = #{addr => Dest,
+ %% ctrl => CMsgHdrs,
+ iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock, nowait) of
+ {ok, #{addr := Source,
+ iov := [Data]}} ->
+ {ok, {Source, Data}};
+ {ok, _} = OK ->
+ OK;
+ {select, _} = SELECT ->
+ SELECT;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet,
+ send => Send,
+ recv => Recv},
+ ok = api_a_send_and_recv_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically send and receive on an IPv6 UDP (dgram) socket using
+%% sendto and recvfrom. But we try to be async. That is, we use
+%% the 'nowait' value for the Timeout argument (and await the eventual
+%% select message). Note that we only do this for the recvmsg,
+%% since its much more difficult to "arrange" for sendmsg.
+%%
+api_a_sendmsg_and_recvmsg_udp6(suite) ->
+ [];
+api_a_sendmsg_and_recvmsg_udp6(doc) ->
+ [];
+api_a_sendmsg_and_recvmsg_udp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_a_sendmsg_and_recvmsg_udp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ Send = fun(Sock, Data, Dest) ->
+ MsgHdr = #{addr => Dest,
+ %% ctrl => CMsgHdrs,
+ iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock, nowait) of
+ {ok, #{addr := Source,
+ iov := [Data]}} ->
+ {ok, {Source, Data}};
+ {ok, _} = OK ->
+ OK;
+ {select, _} = SELECT ->
+ SELECT;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet6,
+ send => Send,
+ recv => Recv},
+ ok = api_a_send_and_recv_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_a_send_and_recv_udp(InitState) ->
+ ServerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, dgram, udp) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind socket (to local address)",
+ cmd => fun(#{sock := Sock, local_sa := LSA} = State) ->
+ case sock_bind(Sock, LSA) of
+ {ok, Port} ->
+ {ok, State#{port => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, local_sa := LSA, port := Port}) ->
+ ServerSA = LSA#{port => Port},
+ ?SEV_ANNOUNCE_READY(Tester, init, ServerSA),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (recv)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv)
+ end},
+ #{desc => "try recv request (with nowait, expect select)",
+ cmd => fun(#{sock := Sock, recv := Recv} = State) ->
+ case Recv(Sock) of
+ {select, {select_info, Tag, RecvRef}} ->
+ ?SEV_IPRINT("expected select: "
+ "~n Tag: ~p"
+ "~n Ref: ~p", [Tag, RecvRef]),
+ {ok, State#{recv_stag => Tag,
+ recv_sref => RecvRef}};
+ {ok, X} ->
+ {error, {unexpected_succes, X}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv_select)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_select),
+ ok
+ end},
+ #{desc => "await select message",
+ cmd => fun(#{sock := Sock, recv_sref := RecvRef}) ->
+ receive
+ {'$socket', Sock, select, RecvRef} ->
+ ok
+ after 5000 ->
+ ?SEV_EPRINT("message queue: ~p", [mq()]),
+ {error, timeout}
+ end
+ end},
+ #{desc => "announce ready (select)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, select),
+ ok
+ end},
+ #{desc => "now read the data (request)",
+ cmd => fun(#{sock := Sock, recv := Recv} = State) ->
+ case Recv(Sock) of
+ {ok, {Src, ?BASIC_REQ}} ->
+ {ok, State#{req_src => Src}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv request)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_req),
+ ok
+ end},
+
+ #{desc => "await continue (send reply)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply)
+ end},
+ #{desc => "send reply",
+ cmd => fun(#{sock := Sock, req_src := Src, send := Send}) ->
+ Send(Sock, ?BASIC_REP, Src)
+ end},
+ #{desc => "announce ready (send)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send),
+ ok
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ State2 = maps:remove(tester, State),
+ State3 = maps:remove(recv_stag, State2),
+ State4 = maps:remove(recv_sref, State3),
+ State5 = maps:remove(req_src, State4),
+ {ok, State5};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ ClientSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ {Tester, ServerSA} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ server_sa => ServerSA}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa => LSA}}
+ end},
+ #{desc => "open socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ Sock = sock_open(Domain, dgram, udp),
+ SA = sock_sockname(Sock),
+ {ok, State#{sock => Sock, sa => SA}}
+ end},
+ #{desc => "bind socket (to local address)",
+ cmd => fun(#{sock := Sock, lsa := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (send request)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_req)
+ end},
+ #{desc => "send request",
+ cmd => fun(#{sock := Sock, server_sa := Server, send := Send}) ->
+ Send(Sock, ?BASIC_REQ, Server)
+ end},
+ #{desc => "announce ready (send request)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_req),
+ ok
+ end},
+ #{desc => "await continue (recv)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv)
+ end},
+ #{desc => "try recv reply (with nowait)",
+ cmd => fun(#{sock := Sock, recv := Recv} = State) ->
+ case Recv(Sock) of
+ {select, {select_info, Tag, RecvRef}} ->
+ {ok, State#{recv_stag => Tag,
+ recv_sref => RecvRef}};
+ {ok, X} ->
+ {error, {unexpected_select_info, X}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv_select)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_select),
+ ok
+ end},
+ #{desc => "await select message",
+ cmd => fun(#{sock := Sock, recv_sref := RecvRef}) ->
+ receive
+ {'$socket', Sock, select, RecvRef} ->
+ ok
+ end
+ end},
+ #{desc => "announce ready (select)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, select),
+ ok
+ end},
+ #{desc => "now read the data (reply)",
+ cmd => fun(#{sock := Sock, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {_Src, ?BASIC_REP}} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_rep),
+ ok
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ State2 = maps:remove(tester, State),
+ State3 = maps:remove(recv_stag, State2),
+ State4 = maps:remove(recv_sref, State3),
+ {ok, State4};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = State) ->
+ {ok, ServerSA} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{server_sa => ServerSA}}
+ end},
+
+ %% Start the client
+ #{desc => "order client start",
+ cmd => fun(#{client := Pid,
+ server_sa := ServerSA} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, ServerSA),
+ ok
+ end},
+ #{desc => "await client ready (init)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client, init)
+ end},
+
+ %% The actual test
+ #{desc => "order server continue (recv)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ #{desc => "await server ready (recv_select)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, server, recv_select)
+ end},
+
+ #{desc => "order client continue (send request)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, send_req),
+ ok
+ end},
+ #{desc => "await client ready (send request)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client, send_req)
+ end},
+ #{desc => "await server ready (select)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, server, select)
+ end},
+ #{desc => "await server ready (recv request)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, server, recv_req)
+ end},
+
+ #{desc => "order client continue (recv)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ #{desc => "await client ready (recv_select)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client, recv_select)
+ end},
+ #{desc => "order server continue (send reply)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, send_reply),
+ ok
+ end},
+ #{desc => "await server ready (send)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, server, send)
+ end},
+ #{desc => "await client ready (select)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client, select)
+ end},
+ #{desc => "await client ready (recv reply)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client, recv_rep)
+ end},
+
+ %% Terminations
+ #{desc => "order client to terminate",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await client termination",
+ cmd => fun(#{client := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(client, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(server, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start server evaluator"),
+ ServerInitState = InitState,
+ Server = ?SEV_START("server", ServerSeq, ServerInitState),
+
+ i("start client evaluator(s)"),
+ ClientInitState = InitState,
+ Client = ?SEV_START("client", ClientSeq, ClientInitState),
+
+ i("start 'tester' evaluator"),
+ TesterInitState = #{server => Server#ev.pid,
+ client => Client#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically send and receive using the "common" functions (send and recv)
+%% on an IPv4 TCP (stream) socket. But we try to be async. That is, we use
+%% the 'nowait' value for the Timeout argument (and await the eventual
+%% select message). Note that we only do this for the recv,
+%% since its much more difficult to "arrange" for send.
+%% We *also* test async for accept.
+api_a_send_and_recv_tcp4(suite) ->
+ [];
+api_a_send_and_recv_tcp4(doc) ->
+ [];
+api_a_send_and_recv_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_a_send_and_recv_tcp4,
+ fun() ->
+ Send = fun(Sock, Data) ->
+ socket:send(Sock, Data)
+ end,
+ Recv = fun(Sock) ->
+ socket:recv(Sock, 0, nowait)
+ end,
+ InitState = #{domain => inet,
+ send => Send,
+ recv => Recv},
+ ok = api_a_send_and_recv_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically send and receive using the "common" functions (send and recv)
+%% on an IPv6 TCP (stream) socket. But we try to be async. That is, we use
+%% the 'nowait' value for the Timeout argument (and await the eventual
+%% select message). Note that we only do this for the recv,
+%% since its much more difficult to "arrange" for send.
+%% We *also* test async for accept.
+api_a_send_and_recv_tcp6(suite) ->
+ [];
+api_a_send_and_recv_tcp6(doc) ->
+ [];
+api_a_send_and_recv_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_a_send_and_recv_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ Send = fun(Sock, Data) ->
+ socket:send(Sock, Data)
+ end,
+ Recv = fun(Sock) ->
+ socket:recv(Sock, 0, nowait)
+ end,
+ InitState = #{domain => inet6,
+ send => Send,
+ recv => Recv},
+ ok = api_a_send_and_recv_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically send and receive using the msg functions (sendmsg and recvmsg)
+%% on an IPv4 TCP (stream) socket. But we try to be async. That is, we use
+%% the 'nowait' value for the Timeout argument (and await the eventual
+%% select message). Note that we only do this for the recvmsg,
+%% since its much more difficult to "arrange" for sendmsg.
+%% We *also* test async for accept.
+api_a_sendmsg_and_recvmsg_tcp4(suite) ->
+ [];
+api_a_sendmsg_and_recvmsg_tcp4(doc) ->
+ [];
+api_a_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_a_sendmsg_and_recvmsg_tcp4,
+ fun() ->
+ Send = fun(Sock, Data) ->
+ MsgHdr = #{iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock, nowait) of
+ {ok, #{addr := undefined,
+ iov := [Data]}} ->
+ {ok, Data};
+ {ok, _} = OK ->
+ OK;
+ {select, _} = SELECT ->
+ SELECT;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet,
+ send => Send,
+ recv => Recv},
+ ok = api_a_send_and_recv_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically send and receive using the msg functions (sendmsg and recvmsg)
+%% on an IPv6 TCP (stream) socket. But we try to be async. That is, we use
+%% the 'nowait' value for the Timeout argument (and await the eventual
+%% select message). Note that we only do this for the recvmsg,
+%% since its much more difficult to "arrange" for sendmsg.
+%% We *also* test async for accept.
+api_a_sendmsg_and_recvmsg_tcp6(suite) ->
+ [];
+api_a_sendmsg_and_recvmsg_tcp6(doc) ->
+ [];
+api_a_sendmsg_and_recvmsg_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_a_sendmsg_and_recvmsg_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ Send = fun(Sock, Data) ->
+ MsgHdr = #{iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock, nowait) of
+ {ok, #{addr := undefined,
+ iov := [Data]}} ->
+ {ok, Data};
+ {ok, _} = OK ->
+ OK;
+ {select, _} = SELECT ->
+ SELECT;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet6,
+ send => Send,
+ recv => Recv},
+ ok = api_a_send_and_recv_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_a_send_and_recv_tcp(InitState) ->
+ process_flag(trap_exit, true),
+ ServerSeq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa => LSA}}
+ end},
+ #{desc => "create listen socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{lsock := LSock, lsa := LSA} = State) ->
+ case sock_bind(LSock, LSA) of
+ {ok, Port} ->
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, lport := Port}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Port),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "await connection (nowait)",
+ cmd => fun(#{lsock := LSock} = State) ->
+ case socket:accept(LSock, nowait) of
+ {select, {select_info, Tag, Ref}} ->
+ ?SEV_IPRINT("accept select: "
+ "~n Tag: ~p"
+ "~n Ref: ~p", [Tag, Ref]),
+ {ok, State#{accept_stag => Tag,
+ accept_sref => Ref}};
+ {ok, X} ->
+ {error, {unexpected_select_info, X}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (accept_select)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept_select),
+ ok
+ end},
+ #{desc => "await select message",
+ cmd => fun(#{lsock := Sock, accept_sref := Ref}) ->
+ receive
+ {'$socket', Sock, select, Ref} ->
+ ok
+ end
+ end},
+ #{desc => "announce ready (select)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, select),
+ ok
+ end},
+ #{desc => "await connection (again)",
+ cmd => fun(#{lsock := LSock} = State) ->
+ case socket:accept(LSock, nowait) of
+ {ok, Sock} ->
+ ?SEV_IPRINT("accepted: "
+ "~n Sock: ~p", [Sock]),
+ {ok, State#{csock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept),
+ ok
+ end},
+
+ #{desc => "await continue (recv request)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv_req)
+ end},
+ #{desc => "try recv request (with nowait, expect select)",
+ cmd => fun(#{csock := Sock, recv := Recv} = State) ->
+ case Recv(Sock) of
+ {select, {select_info, Tag, Ref}} ->
+ ?SEV_IPRINT("recv select: "
+ "~n Tag: ~p"
+ "~n Ref: ~p", [Tag, Ref]),
+ {ok, State#{recv_stag => Tag,
+ recv_sref => Ref}};
+ {ok, X} ->
+ {error, {unexpected_select_info, X}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv_select)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_select),
+ ok
+ end},
+ #{desc => "await select message",
+ cmd => fun(#{csock := Sock, recv_sref := RecvRef}) ->
+ receive
+ {'$socket', Sock, select, RecvRef} ->
+ ok
+ end
+ end},
+ #{desc => "announce ready (select)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, select),
+ ok
+ end},
+ #{desc => "now read the data (request)",
+ cmd => fun(#{csock := Sock, recv := Recv} = _State) ->
+ case Recv(Sock) of
+ {ok, ?BASIC_REQ} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv request)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_req),
+ ok
+ end},
+
+ #{desc => "await continue (send reply)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_rep)
+ end},
+ #{desc => "send reply",
+ cmd => fun(#{csock := Sock, send := Send}) ->
+ Send(Sock, ?BASIC_REP)
+ end},
+ #{desc => "announce ready (send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_rep),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close connection socket",
+ cmd => fun(#{csock := Sock}) ->
+ socket:close(Sock)
+ end},
+ #{desc => "close listen socket",
+ cmd => fun(#{lsock := Sock}) ->
+ socket:close(Sock)
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ ClientSeq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ {Tester, Port} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, server_port => Port}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** The init part ***
+ #{desc => "which server (local) address",
+ cmd => fun(#{domain := Domain, server_port := Port} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ SSA = LSA#{port => Port},
+ {ok, State#{local_sa => LSA, server_sa => SSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{sock := Sock, local_sa := LSA} = _State) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% *** The actual test ***
+ #{desc => "await continue (connect)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, connect)
+ end},
+ #{desc => "connect to server",
+ cmd => fun(#{sock := Sock, server_sa := SSA}) ->
+ socket:connect(Sock, SSA)
+ end},
+ #{desc => "announce ready (connect)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, connect),
+ ok
+ end},
+
+ #{desc => "await continue (send request)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_req)
+ end},
+ #{desc => "send request (to server)",
+ cmd => fun(#{sock := Sock, send := Send}) ->
+ ok = Send(Sock, ?BASIC_REQ)
+ end},
+ #{desc => "announce ready (send request)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_req),
+ ok
+ end},
+
+ #{desc => "try recv reply (with nowait, expect select)",
+ cmd => fun(#{sock := Sock, recv := Recv} = State) ->
+ case Recv(Sock) of
+ {select, {select_info, Tag, Ref}} ->
+ ?SEV_IPRINT("recv select: "
+ "~n Tag: ~p"
+ "~n Ref: ~p", [Tag, Ref]),
+ {ok, State#{recv_stag => Tag,
+ recv_sref => Ref}};
+ {ok, X} ->
+ {error, {unexpected_select_info, X}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv_select)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_select),
+ ok
+ end},
+ #{desc => "await select message",
+ cmd => fun(#{sock := Sock, recv_sref := RecvRef}) ->
+ receive
+ {'$socket', Sock, select, RecvRef} ->
+ ok
+ end
+ end},
+ #{desc => "announce ready (select)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, select),
+ ok
+ end},
+ #{desc => "now read the data (reply)",
+ cmd => fun(#{sock := Sock, recv := Recv}) ->
+ {ok, ?BASIC_REP} = Recv(Sock),
+ ok
+ end},
+ #{desc => "announce ready (recv reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_rep),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock}) ->
+ socket:close(Sock)
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = State) ->
+ {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{server_port => Port}}
+ end},
+
+ %% Start the client
+ #{desc => "order client start",
+ cmd => fun(#{client := Pid, server_port := Port} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Port),
+ ok
+ end},
+ #{desc => "await client ready (init)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client, init)
+ end},
+
+ %% *** The actual test ***
+ #{desc => "order server to continue (with accept)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, accept),
+ ok
+ end},
+ #{desc => "await server ready (accept select)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, server, accept_select)
+ end},
+ #{desc => "order client to continue (with connect)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, connect),
+ ok
+ end},
+ #{desc => "await server ready (select)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, server, select)
+ end},
+ #{desc => "await server ready (accept)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, server, accept)
+ end},
+ #{desc => "await client ready (connect)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, client, connect)
+ end},
+
+ #{desc => "order server to continue (recv request)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv_req),
+ ok
+ end},
+ #{desc => "await server ready (recv select)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, server, recv_select)
+ end},
+ #{desc => "order client to continue (send request)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, send_req),
+ ok
+ end},
+ #{desc => "await client ready (send request)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, send_req)
+ end},
+ #{desc => "await server ready (select)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, server, select)
+ end},
+ #{desc => "await server ready (recv request)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, server, recv_req)
+ end},
+
+ #{desc => "order client to continue (recv reply)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv_rep),
+ ok
+ end},
+ #{desc => "await client ready (recv select)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, client, recv_select)
+ end},
+ #{desc => "order server to continue (send reply)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, send_rep),
+ ok
+ end},
+ #{desc => "await server ready (send reply)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, server, send_rep)
+ end},
+ #{desc => "await client ready (select)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, client, select)
+ end},
+ #{desc => "await client ready (reply recv)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, recv_rep)
+ end},
+
+
+ %% *** Termination ***
+ #{desc => "order client to terminate",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await client termination",
+ cmd => fun(#{client := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(client, State),
+ {ok, State1}
+ end},
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Server),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Server} = State) ->
+ ?SEV_AWAIT_TERMINATION(Server),
+ State1 = maps:remove(server, State),
+ {ok, State1}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start server evaluator"),
+ Server = ?SEV_START("server", ServerSeq, InitState),
+
+ i("start client evaluator"),
+ Client = ?SEV_START("client", ClientSeq, InitState),
+
+ i("start tester evaluator"),
+ TesterInitState = #{server => Server#ev.pid,
+ client => Client#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator(s)"),
+ ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]).
+
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically we make an async (Timeout = nowait) call to recvfrom,
+%% wait some time and then cancel. IPv4
+%%
+api_a_recvfrom_cancel_udp4(suite) ->
+ [];
+api_a_recvfrom_cancel_udp4(doc) ->
+ [];
+api_a_recvfrom_cancel_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_a_recvfrom_cancel_udp4,
+ fun() ->
+ Recv = fun(Sock) ->
+ case socket:recvfrom(Sock, 0, nowait) of
+ {ok, _} = OK ->
+ OK;
+ {select, _} = SELECT ->
+ SELECT;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet,
+ recv => Recv},
+ ok = api_a_recv_cancel_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically we make an async (Timeout = nowait) call to recvfrom,
+%% wait some time and then cancel. IPv6
+%%
+api_a_recvfrom_cancel_udp6(suite) ->
+ [];
+api_a_recvfrom_cancel_udp6(doc) ->
+ [];
+api_a_recvfrom_cancel_udp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_a_recvfrom_cancel_udp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ Recv = fun(Sock) ->
+ case socket:recvfrom(Sock, 0, nowait) of
+ {ok, _} = OK ->
+ OK;
+ {select, _} = SELECT ->
+ SELECT;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet6,
+ recv => Recv},
+ ok = api_a_recv_cancel_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically we make an async (Timeout = nowait) call to recvmsg,
+%% wait some time and then cancel. IPv4
+%%
+api_a_recvmsg_cancel_udp4(suite) ->
+ [];
+api_a_recvmsg_cancel_udp4(doc) ->
+ [];
+api_a_recvmsg_cancel_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_a_recvmsg_cancel_udp4,
+ fun() ->
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock, nowait) of
+ {ok, _} = OK ->
+ OK;
+ {select, _} = SELECT ->
+ SELECT;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet,
+ recv => Recv},
+ ok = api_a_recv_cancel_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically we make an async (Timeout = nowait) call to recvmsg,
+%% wait some time and then cancel. IPv6
+%%
+api_a_recvmsg_cancel_udp6(suite) ->
+ [];
+api_a_recvmsg_cancel_udp6(doc) ->
+ [];
+api_a_recvmsg_cancel_udp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_a_recvmsg_cancel_udp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock, nowait) of
+ {ok, _} = OK ->
+ OK;
+ {select, _} = SELECT ->
+ SELECT;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet6,
+ recv => Recv},
+ ok = api_a_recv_cancel_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_a_recv_cancel_udp(InitState) ->
+ ServerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, dgram, udp) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind socket (to local address)",
+ cmd => fun(#{sock := Sock, local_sa := LSA} = State) ->
+ case sock_bind(Sock, LSA) of
+ {ok, Port} ->
+ {ok, State#{port => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, local_sa := LSA, port := Port}) ->
+ ServerSA = LSA#{port => Port},
+ ?SEV_ANNOUNCE_READY(Tester, init, ServerSA),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (recv)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv)
+ end},
+ #{desc => "try recv request (with nowait, expect select)",
+ cmd => fun(#{sock := Sock, recv := Recv} = State) ->
+ case Recv(Sock) of
+ {select, SelectInfo} ->
+ {ok, State#{recv_select_info => SelectInfo}};
+ {ok, X} ->
+ {error, {unexpected_select_info, X}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv_select)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_select),
+ ok
+ end},
+ #{desc => "await select message (without success)",
+ cmd => fun(#{sock := Sock}) ->
+ receive
+ {'$socket', Sock, select, Ref} ->
+ {error, {unexpected_select, Ref}}
+ after 5000 ->
+ ok
+ end
+ end},
+ #{desc => "announce ready (no select)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, no_select),
+ ok
+ end},
+ #{desc => "await continue (cancel)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, cancel)
+ end},
+ #{desc => "cancel",
+ cmd => fun(#{sock := Sock, recv_select_info := SelectInfo}) ->
+ ok = socket:cancel(Sock, SelectInfo)
+ end},
+ #{desc => "announce ready (cancel)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, cancel),
+ ok
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ State2 = maps:remove(tester, State),
+ State3 = maps:remove(recv_stag, State2),
+ State4 = maps:remove(recv_sref, State3),
+ State5 = maps:remove(req_src, State4),
+ {ok, State5};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = State) ->
+ {ok, ServerSA} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{server_sa => ServerSA}}
+ end},
+
+ %% The actual test
+ #{desc => "order server continue (recv)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ #{desc => "await server ready (recv select)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, server, recv_select)
+ end},
+ #{desc => "await server ready (no select)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, server, no_select)
+ end},
+ #{desc => "order server continue (cancel)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, cancel),
+ ok
+ end},
+ #{desc => "await server ready (cancel)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, server, cancel)
+ end},
+
+ %% Terminations
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(server, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start server evaluator"),
+ ServerInitState = InitState,
+ Server = ?SEV_START("server", ServerSeq, ServerInitState),
+
+ i("start 'tester' evaluator"),
+ TesterInitState = #{server => Server#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Server, Tester]).
+
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically we make an async (Timeout = nowait) call to accept,
+%% wait some time and then cancel. IPv4
+%%
+api_a_accept_cancel_tcp4(suite) ->
+ [];
+api_a_accept_cancel_tcp4(doc) ->
+ [];
+api_a_accept_cancel_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_a_accept_cancel_tcp4,
+ fun() ->
+ Accept = fun(Sock) ->
+ case socket:accept(Sock, nowait) of
+ {ok, _} = OK ->
+ OK;
+ {select, _} = SELECT ->
+ SELECT;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet,
+ accept => Accept},
+ ok = api_a_accept_cancel_tcp(InitState)
+ end).
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically we make an async (Timeout = nowait) call to accept,
+%% wait some time and then cancel. IPv6
+%%
+api_a_accept_cancel_tcp6(suite) ->
+ [];
+api_a_accept_cancel_tcp6(doc) ->
+ [];
+api_a_accept_cancel_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_a_accept_cancel_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ Accept = fun(Sock) ->
+ case socket:accept(Sock, nowait) of
+ {ok, _} = OK ->
+ OK;
+ {select, _} = SELECT ->
+ SELECT;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet6,
+ accept => Accept},
+ ok = api_a_accept_cancel_tcp(InitState)
+ end).
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_a_accept_cancel_tcp(InitState) ->
+ process_flag(trap_exit, true),
+ ServerSeq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa => LSA}}
+ end},
+ #{desc => "create listen socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{lsock := LSock, lsa := LSA} = State) ->
+ case sock_bind(LSock, LSA) of
+ {ok, Port} ->
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, lport := Port}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Port),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "await connection (nowait)",
+ cmd => fun(#{lsock := LSock, accept := Accept} = State) ->
+ case Accept(LSock) of
+ {select, {select_info, T, R} = SelectInfo} ->
+ ?SEV_IPRINT("accept select: "
+ "~n T: ~p"
+ "~n R: ~p", [T, R]),
+ {ok, State#{accept_select_info => SelectInfo}};
+ {ok, X} ->
+ {error, {unexpected_select_info, X}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (accept_select)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept_select),
+ ok
+ end},
+ #{desc => "await select message (without success)",
+ cmd => fun(#{lsock := Sock}) ->
+ receive
+ {'$socket', Sock, select, Ref} ->
+ {error, {unexpected_select, Ref}}
+ after 5000 ->
+ ok
+ end
+ end},
+ #{desc => "announce ready (no select)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, no_select),
+ ok
+ end},
+ #{desc => "await continue (cancel)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, cancel)
+ end},
+ #{desc => "cancel",
+ cmd => fun(#{lsock := Sock, accept_select_info := SelectInfo}) ->
+ ok = socket:cancel(Sock, SelectInfo)
+ end},
+ #{desc => "announce ready (cancel)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, cancel),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close listen socket",
+ cmd => fun(#{lsock := Sock}) ->
+ socket:close(Sock)
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = State) ->
+ {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{server_port => Port}}
+ end},
+
+ %% *** The actual test ***
+ #{desc => "order server to continue (with accept)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, accept),
+ ok
+ end},
+ #{desc => "await server ready (accept select)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, server, accept_select)
+ end},
+ #{desc => "await server ready (no select)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, server, no_select)
+ end},
+ #{desc => "order server to continue (cancel)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, cancel),
+ ok
+ end},
+ #{desc => "await server ready (cancel)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, server, cancel)
+ end},
+
+ %% *** Termination ***
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Server),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Server} = State) ->
+ ?SEV_AWAIT_TERMINATION(Server),
+ State1 = maps:remove(server, State),
+ {ok, State1}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start server evaluator"),
+ Server = ?SEV_START("server", ServerSeq, InitState),
+
+ i("start tester evaluator"),
+ TesterInitState = #{server => Server#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator(s)"),
+ ok = ?SEV_AWAIT_FINISH([Server, Tester]).
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically we make an async (Timeout = nowait) call to recv,
+%% wait some time and then cancel. IPv4
+%%
+api_a_recv_cancel_tcp4(suite) ->
+ [];
+api_a_recv_cancel_tcp4(doc) ->
+ [];
+api_a_recv_cancel_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_a_recv_cancel_tcp4,
+ fun() ->
+ Recv = fun(Sock) ->
+ socket:recv(Sock, 0, nowait)
+ end,
+ InitState = #{domain => inet,
+ recv => Recv},
+ ok = api_a_recv_cancel_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically we make an async (Timeout = nowait) call to recv,
+%% wait some time and then cancel. IPv6
+%%
+api_a_recv_cancel_tcp6(suite) ->
+ [];
+api_a_recv_cancel_tcp6(doc) ->
+ [];
+api_a_recv_cancel_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_a_recv_cancel_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ Recv = fun(Sock) ->
+ socket:recv(Sock, 0, nowait)
+ end,
+ InitState = #{domain => inet6,
+ recv => Recv},
+ ok = api_a_recv_cancel_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically we make an async (Timeout = nowait) call to recvmsg,
+%% wait some time and then cancel. IPv4
+%%
+api_a_recvmsg_cancel_tcp4(suite) ->
+ [];
+api_a_recvmsg_cancel_tcp4(doc) ->
+ [];
+api_a_recvmsg_cancel_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_a_recvmsg_cancel_tcp4,
+ fun() ->
+ Recv = fun(Sock) ->
+ socket:recvmsg(Sock, nowait)
+ end,
+ InitState = #{domain => inet,
+ recv => Recv},
+ ok = api_a_recv_cancel_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically we make an async (Timeout = nowait) call to recvmsg,
+%% wait some time and then cancel. IPv6
+%%
+api_a_recvmsg_cancel_tcp6(suite) ->
+ [];
+api_a_recvmsg_cancel_tcp6(doc) ->
+ [];
+api_a_recvmsg_cancel_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_a_recvmsg_cancel_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ Recv = fun(Sock) ->
+ socket:recvmsg(Sock, nowait)
+ end,
+ InitState = #{domain => inet6,
+ recv => Recv},
+ ok = api_a_recv_cancel_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_a_recv_cancel_tcp(InitState) ->
+ process_flag(trap_exit, true),
+ ServerSeq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa => LSA}}
+ end},
+ #{desc => "create listen socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{lsock := LSock, lsa := LSA} = State) ->
+ case sock_bind(LSock, LSA) of
+ {ok, Port} ->
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, lport := Port}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Port),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "await connection (nowait)",
+ cmd => fun(#{lsock := LSock} = State) ->
+ case socket:accept(LSock) of
+ {ok, CSock} ->
+ {ok, State#{csock => CSock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept),
+ ok
+ end},
+
+ #{desc => "await continue (nowait recv)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv)
+ end},
+ #{desc => "try recv request (with nowait, expect select)",
+ cmd => fun(#{csock := Sock, recv := Recv} = State) ->
+ case Recv(Sock) of
+ {select, {select_info, T, R} = SelectInfo} ->
+ ?SEV_IPRINT("recv select: "
+ "~n Tag: ~p"
+ "~n Ref: ~p", [T, R]),
+ {ok, State#{recv_select_info => SelectInfo}};
+ {ok, X} ->
+ {error, {unexpected_select_info, X}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv_select)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_select),
+ ok
+ end},
+ #{desc => "await select message",
+ cmd => fun(#{csock := Sock}) ->
+ receive
+ {'$socket', Sock, select, Ref} ->
+ {error, {unexpected_select, Ref}}
+ after 5000 ->
+ ok
+ end
+ end},
+ #{desc => "announce ready (no select)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, no_select),
+ ok
+ end},
+ #{desc => "await continue (cancel)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, cancel)
+ end},
+ #{desc => "cancel",
+ cmd => fun(#{csock := Sock, recv_select_info := SelectInfo}) ->
+ ok = socket:cancel(Sock, SelectInfo)
+ end},
+ #{desc => "announce ready (cancel)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, cancel),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close connection socket",
+ cmd => fun(#{csock := Sock}) ->
+ socket:close(Sock)
+ end},
+ #{desc => "close listen socket",
+ cmd => fun(#{lsock := Sock}) ->
+ socket:close(Sock)
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ ClientSeq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ {Tester, Port} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, server_port => Port}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** The init part ***
+ #{desc => "which server (local) address",
+ cmd => fun(#{domain := Domain, server_port := Port} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ SSA = LSA#{port => Port},
+ {ok, State#{local_sa => LSA, server_sa => SSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{sock := Sock, local_sa := LSA} = _State) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% *** The actual test ***
+ #{desc => "await continue (connect)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, connect)
+ end},
+ #{desc => "connect to server",
+ cmd => fun(#{sock := Sock, server_sa := SSA}) ->
+ socket:connect(Sock, SSA)
+ end},
+ #{desc => "announce ready (connect)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, connect),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock}) ->
+ socket:close(Sock)
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = State) ->
+ {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{server_port => Port}}
+ end},
+
+ %% Start the client
+ #{desc => "order client start",
+ cmd => fun(#{client := Pid, server_port := Port} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Port),
+ ok
+ end},
+ #{desc => "await client ready (init)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client, init)
+ end},
+
+ %% *** The actual test ***
+ #{desc => "order server to continue (with accept)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, accept),
+ ok
+ end},
+ #{desc => "order client to continue (connect)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, connect),
+ ok
+ end},
+ #{desc => "await client ready (connect)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, client, connect)
+ end},
+ #{desc => "await server ready (accept)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, server, accept)
+ end},
+
+ #{desc => "order server to continue (recv)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ #{desc => "await server ready (recv select)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, server, recv_select)
+ end},
+ #{desc => "await server ready (no select)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, server, no_select)
+ end},
+ #{desc => "order server to continue (send request)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, cancel),
+ ok
+ end},
+ #{desc => "await server ready (cancel)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, server, cancel)
+ end},
+
+ %% *** Termination ***
+ #{desc => "order client to terminate",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await client termination",
+ cmd => fun(#{client := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(client, State),
+ {ok, State1}
+ end},
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Server),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Server} = State) ->
+ ?SEV_AWAIT_TERMINATION(Server),
+ State1 = maps:remove(server, State),
+ {ok, State1}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start server evaluator"),
+ Server = ?SEV_START("server", ServerSeq, InitState),
+
+ i("start client evaluator"),
+ Client = ?SEV_START("client", ClientSeq, InitState),
+
+ i("start tester evaluator"),
+ TesterInitState = #{server => Server#ev.pid,
+ client => Client#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator(s)"),
+ ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]).
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically we make multiple async (Timeout = nowait) call(s) to recvfrom
+%% (from *several* processes), wait some time and then cancel.
+%% This should result in abort messages to the 'other' processes. IPv4
+%%
+api_a_mrecvfrom_cancel_udp4(suite) ->
+ [];
+api_a_mrecvfrom_cancel_udp4(doc) ->
+ [];
+api_a_mrecvfrom_cancel_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(20)),
+ tc_try(api_a_mrecvfrom_cancel_udp4,
+ fun() ->
+ Recv = fun(Sock) ->
+ case socket:recvfrom(Sock, 0, nowait) of
+ {ok, _} = OK ->
+ OK;
+ {select, _} = SELECT ->
+ SELECT;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet,
+ recv => Recv},
+ ok = api_a_mrecv_cancel_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically we make multiple async (Timeout = nowait) call(s) to recvfrom
+%% (from *several* processes), wait some time and then cancel.
+%% This should result in abort messages to the 'other' processes. IPv6
+%%
+api_a_mrecvfrom_cancel_udp6(suite) ->
+ [];
+api_a_mrecvfrom_cancel_udp6(doc) ->
+ [];
+api_a_mrecvfrom_cancel_udp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(20)),
+ tc_try(api_a_mrecvfrom_cancel_udp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ Recv = fun(Sock) ->
+ case socket:recvfrom(Sock, 0, nowait) of
+ {ok, _} = OK ->
+ OK;
+ {select, _} = SELECT ->
+ SELECT;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet6,
+ recv => Recv},
+ ok = api_a_mrecv_cancel_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically we make multiple async (Timeout = nowait) call(s) to recvmsg
+%% (from *several* processes), wait some time and then cancel.
+%% This should result in abort messages to the 'other' processes. IPv4
+%%
+api_a_mrecvmsg_cancel_udp4(suite) ->
+ [];
+api_a_mrecvmsg_cancel_udp4(doc) ->
+ [];
+api_a_mrecvmsg_cancel_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(20)),
+ tc_try(api_a_mrecvmsg_cancel_udp4,
+ fun() ->
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock, nowait) of
+ {ok, _} = OK ->
+ OK;
+ {select, _} = SELECT ->
+ SELECT;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet,
+ recv => Recv},
+ ok = api_a_mrecv_cancel_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically we make multiple async (Timeout = nowait) call(s) to recvmsg
+%% (from *several* processes), wait some time and then cancel.
+%% This should result in abort messages to the 'other' processes. IPv6
+%%
+api_a_mrecvmsg_cancel_udp6(suite) ->
+ [];
+api_a_mrecvmsg_cancel_udp6(doc) ->
+ [];
+api_a_mrecvmsg_cancel_udp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(20)),
+ tc_try(api_a_mrecvmsg_cancel_udp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock, nowait) of
+ {ok, _} = OK ->
+ OK;
+ {select, _} = SELECT ->
+ SELECT;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet6,
+ recv => Recv},
+ ok = api_a_mrecv_cancel_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_a_mrecv_cancel_udp(InitState) ->
+ ServerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, dgram, udp) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind socket (to local address)",
+ cmd => fun(#{sock := Sock, local_sa := LSA} = State) ->
+ case sock_bind(Sock, LSA) of
+ {ok, Port} ->
+ {ok, State#{port => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, sock := Sock}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Sock),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (recv)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv)
+ end},
+ #{desc => "try recv request (with nowait, expect select)",
+ cmd => fun(#{sock := Sock, recv := Recv} = State) ->
+ case Recv(Sock) of
+ {select, SelectInfo} ->
+ {ok, State#{recv_select_info => SelectInfo}};
+ {ok, X} ->
+ {error, {unexpected_select_info, X}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv_select)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_select),
+ ok
+ end},
+ #{desc => "await abort message",
+ cmd => fun(#{sock := Sock,
+ recv_select_info := {select_info, _, Ref}} = State) ->
+ receive
+ {'$socket', Sock, select, Ref} ->
+ {error, {unexpected_select, Ref}};
+ {'$socket', Sock, abort, {Ref, closed}} ->
+ {ok, maps:remove(sock, State)}
+ after 5000 ->
+ ?SEV_EPRINT("message queue: ~p", [mq()]),
+ {error, timeout}
+ end
+ end},
+ #{desc => "announce ready (abort)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, abort),
+ ok
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ State2 = maps:remove(tester, State),
+ State3 = maps:remove(recv_stag, State2),
+ State4 = maps:remove(recv_sref, State3),
+ State5 = maps:remove(req_src, State4),
+ {ok, State5};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ AltServerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ {Tester, Sock} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, sock => Sock}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (recv)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv)
+ end},
+ #{desc => "try recv request (with nowait, expect select)",
+ cmd => fun(#{sock := Sock, recv := Recv} = State) ->
+ case Recv(Sock) of
+ {select, SelectInfo} ->
+ {ok, State#{recv_select_info => SelectInfo}};
+ {ok, X} ->
+ {error, {unexpected_select_info, X}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv_select)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_select),
+ ok
+ end},
+ #{desc => "await abort message",
+ cmd => fun(#{sock := Sock,
+ recv_select_info := {select_info, _, Ref}} = State) ->
+ receive
+ {'$socket', Sock, select, Ref} ->
+ {error, {unexpected_select, Ref}};
+ {'$socket', Sock, abort, {Ref, closed}} ->
+ {ok, maps:remove(sock, State)}
+ after 5000 ->
+ ?SEV_EPRINT("message queue: ~p", [mq()]),
+ {error, timeout}
+ end
+ end},
+ #{desc => "announce ready (abort)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, abort),
+ ok
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ ?SEV_IPRINT("terminating"),
+ State1 = maps:remove(recv_select_info, State),
+ State2 = maps:remove(tester, State1),
+ State3 = maps:remove(sock, State2),
+ {ok, State3};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor alt-server 1",
+ cmd => fun(#{alt_server1 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor alt-server 2",
+ cmd => fun(#{alt_server2 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = State) ->
+ {ok, Sock} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{sock => Sock}}
+ end},
+
+ %% Start the alt-server 1
+ #{desc => "order alt-server 1 start",
+ cmd => fun(#{alt_server1 := Pid, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Sock),
+ ok
+ end},
+ #{desc => "await alt-server 1 ready (init)",
+ cmd => fun(#{alt_server1 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, alt_server1, init)
+ end},
+
+ %% Start the alt-server 2
+ #{desc => "order alt-server 2 start",
+ cmd => fun(#{alt_server2 := Pid, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Sock),
+ ok
+ end},
+ #{desc => "await alt-server 2 ready (init)",
+ cmd => fun(#{alt_server2 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, alt_server2, init)
+ end},
+
+
+ %% The actual test
+ #{desc => "order server continue (recv)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ #{desc => "await server ready (recv select)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, server, recv_select)
+ end},
+
+ #{desc => "order alt-server 1 continue (recv)",
+ cmd => fun(#{alt_server1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ #{desc => "await alt-server 1 ready (recv select)",
+ cmd => fun(#{alt_server1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, alt_server1, recv_select)
+ end},
+
+ #{desc => "order alt-server 2 continue (recv)",
+ cmd => fun(#{alt_server2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ #{desc => "await alt-server 2 ready (recv select)",
+ cmd => fun(#{alt_server2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, alt_server2, recv_select)
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "close the socket",
+ cmd => fun(#{sock := Sock} = _State) ->
+ socket:close(Sock)
+ end},
+
+ #{desc => "await server ready (abort)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, server, abort)
+ end},
+ #{desc => "await alt-server 1 ready (abort)",
+ cmd => fun(#{alt_server1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, alt_server1, abort)
+ end},
+ #{desc => "await alt-server 2 ready (abort)",
+ cmd => fun(#{alt_server2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, alt_server2, abort)
+ end},
+
+ %% Terminations
+ #{desc => "order alt-server 2 to terminate",
+ cmd => fun(#{alt_server2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await alt-server 2 termination",
+ cmd => fun(#{alt_server2 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(alt_server2, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "order alt-server 1 to terminate",
+ cmd => fun(#{alt_server1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await alt-server 1 termination",
+ cmd => fun(#{alt_server1 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(alt_server1, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(server, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start server evaluator"),
+ Server = ?SEV_START("server", ServerSeq, InitState),
+
+ i("start alt-server 1 evaluator"),
+ AltServer1 = ?SEV_START("alt_server1", AltServerSeq, InitState),
+
+ i("start alt-server 2 evaluator"),
+ AltServer2 = ?SEV_START("alt_server2", AltServerSeq, InitState),
+
+ i("start 'tester' evaluator"),
+ TesterInitState = #{server => Server#ev.pid,
+ alt_server1 => AltServer1#ev.pid,
+ alt_server2 => AltServer2#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator(s)"),
+ ok = ?SEV_AWAIT_FINISH([Server, AltServer1, AltServer2, Tester]).
+
+
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically we make multiple async (Timeout = nowait) call(s) to accept
+%% (from *several* processes), wait some time and then cancel,
+%% This should result in abort messages to the 'other' processes. IPv4
+%%
+api_a_maccept_cancel_tcp4(suite) ->
+ [];
+api_a_maccept_cancel_tcp4(doc) ->
+ [];
+api_a_maccept_cancel_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(20)),
+ tc_try(api_a_maccept_cancel_tcp4,
+ fun() ->
+ Accept = fun(Sock) ->
+ case socket:accept(Sock, nowait) of
+ {ok, _} = OK ->
+ OK;
+ {select, _} = SELECT ->
+ SELECT;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet,
+ accept => Accept},
+ ok = api_a_maccept_cancel_tcp(InitState)
+ end).
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically we make multiple async (Timeout = nowait) call(s) to accept
+%% (from *several* processes), wait some time and then cancel,
+%% This should result in abort messages to the 'other' processes. IPv6
+%%
+api_a_maccept_cancel_tcp6(suite) ->
+ [];
+api_a_maccept_cancel_tcp6(doc) ->
+ [];
+api_a_maccept_cancel_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(20)),
+ tc_try(api_a_maccept_cancel_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ Accept = fun(Sock) ->
+ case socket:accept(Sock, nowait) of
+ {ok, _} = OK ->
+ OK;
+ {select, _} = SELECT ->
+ SELECT;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet6,
+ accept => Accept},
+ ok = api_a_maccept_cancel_tcp(InitState)
+ end).
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_a_maccept_cancel_tcp(InitState) ->
+ process_flag(trap_exit, true),
+ ServerSeq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa => LSA}}
+ end},
+ #{desc => "create listen socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{lsock := LSock, lsa := LSA} = State) ->
+ case sock_bind(LSock, LSA) of
+ {ok, Port} ->
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, lsock := Sock}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Sock),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "await connection (nowait)",
+ cmd => fun(#{lsock := LSock, accept := Accept} = State) ->
+ case Accept(LSock) of
+ {select, {select_info, T, R} = SelectInfo} ->
+ ?SEV_IPRINT("accept select: "
+ "~n T: ~p"
+ "~n R: ~p", [T, R]),
+ {ok, State#{accept_select_info => SelectInfo}};
+ {ok, X} ->
+ {error, {unexpected_select_info, X}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (accept_select)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept_select),
+ ok
+ end},
+ #{desc => "await select message (without success)",
+ cmd => fun(#{lsock := Sock,
+ accept_select_info := {select_info, _, Ref}} = State) ->
+ receive
+ {'$socket', Sock, select, Ref} ->
+ {error, {unexpected_select, Ref}};
+ {'$socket', Sock, abort, {Ref, closed}} ->
+ {ok, maps:remove(lsock, State)}
+ after 5000 ->
+ ?SEV_EPRINT("message queue: ~p", [mq()]),
+ {error, timeout}
+ end
+ end},
+ #{desc => "announce ready (abort)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, abort),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ AltServerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ {Tester, Sock} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, lsock => Sock}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "try accept request (with nowait, expect select)",
+ cmd => fun(#{lsock := Sock, accept := Accept} = State) ->
+ case Accept(Sock) of
+ {select, SelectInfo} ->
+ {ok, State#{accept_select_info => SelectInfo}};
+ {ok, X} ->
+ {error, {unexpected_select_info, X}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (accept_select)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept_select),
+ ok
+ end},
+ #{desc => "await abort message",
+ cmd => fun(#{lsock := Sock,
+ accept_select_info := {select_info, _, Ref}} = State) ->
+ receive
+ {'$socket', Sock, select, Ref} ->
+ {error, {unexpected_select, Ref}};
+ {'$socket', Sock, abort, {Ref, closed}} ->
+ {ok, maps:remove(sock, State)}
+ after 5000 ->
+ ?SEV_EPRINT("message queue: ~p", [mq()]),
+ {error, timeout}
+ end
+ end},
+ #{desc => "announce ready (abort)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, abort),
+ ok
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ ?SEV_IPRINT("terminating"),
+ State1 = maps:remove(tester, State),
+ State2 = maps:remove(accept_select_info, State1),
+ State3 = maps:remove(lsock, State2),
+ {ok, State3};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor alt-server 1",
+ cmd => fun(#{alt_server1 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor alt-server 2",
+ cmd => fun(#{alt_server2 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = State) ->
+ {ok, Sock} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{sock => Sock}}
+ end},
+
+ %% Start the alt-server 1
+ #{desc => "order alt-server 1 start",
+ cmd => fun(#{alt_server1 := Pid, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Sock),
+ ok
+ end},
+ #{desc => "await alt-server 1 ready (init)",
+ cmd => fun(#{alt_server1 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, alt_server1, init)
+ end},
+
+ %% Start the alt-server 2
+ #{desc => "order alt-server 2 start",
+ cmd => fun(#{alt_server2 := Pid, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Sock),
+ ok
+ end},
+ #{desc => "await alt-server 2 ready (init)",
+ cmd => fun(#{alt_server2 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, alt_server2, init)
+ end},
+
+
+ %% *** The actual test ***
+ #{desc => "order server continue (accept)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, accept),
+ ok
+ end},
+ #{desc => "await server ready (accept select)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, server, accept_select)
+ end},
+
+ #{desc => "order alt-server 1 continue (accept)",
+ cmd => fun(#{alt_server1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, accept),
+ ok
+ end},
+ #{desc => "await alt-server 1 ready (accept select)",
+ cmd => fun(#{alt_server1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, alt_server1, accept_select)
+ end},
+
+ #{desc => "order alt-server 2 continue (accept)",
+ cmd => fun(#{alt_server2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, accept),
+ ok
+ end},
+ #{desc => "await alt-server 2 ready (accept select)",
+ cmd => fun(#{alt_server2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, alt_server2, accept_select)
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "close the socket",
+ cmd => fun(#{sock := Sock} = _State) ->
+ socket:close(Sock)
+ end},
+
+ #{desc => "await server ready (abort)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, server, abort)
+ end},
+ #{desc => "await alt-server 1 ready (abort)",
+ cmd => fun(#{alt_server1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, alt_server1, abort)
+ end},
+ #{desc => "await alt-server 2 ready (abort)",
+ cmd => fun(#{alt_server2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, alt_server2, abort)
+ end},
+
+
+ %% *** Termination ***
+ #{desc => "order alt-server 2 to terminate",
+ cmd => fun(#{alt_server2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await alt-server 2 termination",
+ cmd => fun(#{alt_server2 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(alt_server2, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "order alt-server 1 to terminate",
+ cmd => fun(#{alt_server1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await alt-server 1 termination",
+ cmd => fun(#{alt_server1 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(alt_server1, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Server),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Server} = State) ->
+ ?SEV_AWAIT_TERMINATION(Server),
+ State1 = maps:remove(server, State),
+ {ok, State1}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start server evaluator"),
+ Server = ?SEV_START("server", ServerSeq, InitState),
+
+ i("start alt-server 1 evaluator"),
+ AltServer1 = ?SEV_START("alt_server1", AltServerSeq, InitState),
+
+ i("start alt-server 2 evaluator"),
+ AltServer2 = ?SEV_START("alt_server2", AltServerSeq, InitState),
+
+ i("start tester evaluator"),
+ TesterInitState = #{server => Server#ev.pid,
+ alt_server1 => AltServer1#ev.pid,
+ alt_server2 => AltServer2#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator(s)"),
+ ok = ?SEV_AWAIT_FINISH([Server, AltServer1, AltServer2, Tester]).
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically we make multiple async (Timeout = nowait) call(s) to recv
+%% (from *several* processes), wait some time and then cancel,
+%% This should result in abort messages to the 'other' processes. IPv4
+%%
+api_a_mrecv_cancel_tcp4(suite) ->
+ [];
+api_a_mrecv_cancel_tcp4(doc) ->
+ [];
+api_a_mrecv_cancel_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(20)),
+ tc_try(api_a_mrecv_cancel_tcp4,
+ fun() ->
+ Recv = fun(Sock) ->
+ socket:recv(Sock, 0, nowait)
+ end,
+ InitState = #{domain => inet,
+ recv => Recv},
+ ok = api_a_mrecv_cancel_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically we make multiple async (Timeout = nowait) call(s) to recv
+%% (from *several* processes), wait some time and then cancel,
+%% This should result in abort messages to the 'other' processes. IPv6
+%%
+api_a_mrecv_cancel_tcp6(suite) ->
+ [];
+api_a_mrecv_cancel_tcp6(doc) ->
+ [];
+api_a_mrecv_cancel_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(20)),
+ tc_try(api_a_mrecv_cancel_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ Recv = fun(Sock) ->
+ socket:recv(Sock, 0, nowait)
+ end,
+ InitState = #{domain => inet6,
+ recv => Recv},
+ ok = api_a_mrecv_cancel_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically we make multiple async (Timeout = nowait) call(s) to recvmsg
+%% (from *several* processes), wait some time and then cancel,
+%% This should result in abort messages to the 'other' processes. IPv4
+%%
+api_a_mrecvmsg_cancel_tcp4(suite) ->
+ [];
+api_a_mrecvmsg_cancel_tcp4(doc) ->
+ [];
+api_a_mrecvmsg_cancel_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(20)),
+ tc_try(api_a_mrecvmsg_cancel_tcp4,
+ fun() ->
+ Recv = fun(Sock) ->
+ socket:recvmsg(Sock, nowait)
+ end,
+ InitState = #{domain => inet,
+ recv => Recv},
+ ok = api_a_mrecv_cancel_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically we make multiple async (Timeout = nowait) call(s) to recvmsg
+%% (from *several* processes), wait some time and then cancel,
+%% This should result in abort messages to the 'other' processes. IPv6
+%%
+api_a_mrecvmsg_cancel_tcp6(suite) ->
+ [];
+api_a_mrecvmsg_cancel_tcp6(doc) ->
+ [];
+api_a_mrecvmsg_cancel_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(20)),
+ tc_try(api_a_mrecvmsg_cancel_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ Recv = fun(Sock) ->
+ socket:recvmsg(Sock, nowait)
+ end,
+ InitState = #{domain => inet6,
+ recv => Recv},
+ ok = api_a_mrecv_cancel_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_a_mrecv_cancel_tcp(InitState) ->
+ process_flag(trap_exit, true),
+ ServerSeq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa => LSA}}
+ end},
+ #{desc => "create listen socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{lsock := LSock, lsa := LSA} = State) ->
+ case sock_bind(LSock, LSA) of
+ {ok, Port} ->
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, lport := Port}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Port),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "await connection (nowait)",
+ cmd => fun(#{lsock := LSock} = State) ->
+ case socket:accept(LSock) of
+ {ok, CSock} ->
+ {ok, State#{csock => CSock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (accept)",
+ cmd => fun(#{tester := Tester, csock := Sock}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept, Sock),
+ ok
+ end},
+
+ #{desc => "await continue (nowait recv)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv)
+ end},
+ #{desc => "try recv request (with nowait, expect select)",
+ cmd => fun(#{csock := Sock, recv := Recv} = State) ->
+ case Recv(Sock) of
+ {select, {select_info, T, R} = SelectInfo} ->
+ ?SEV_IPRINT("recv select: "
+ "~n Tag: ~p"
+ "~n Ref: ~p", [T, R]),
+ {ok, State#{recv_select_info => SelectInfo}};
+ {ok, X} ->
+ {error, {unexpected_select_info, X}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv_select)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_select),
+ ok
+ end},
+ #{desc => "await select message",
+ cmd => fun(#{csock := Sock,
+ recv_select_info := {select_info, _, Ref}} = State) ->
+ receive
+ {'$socket', Sock, select, Ref} ->
+ {error, {unexpected_select, Ref}};
+ {'$socket', Sock, abort, {Ref, closed}} ->
+ {ok, maps:remove(sock, State)}
+ after 5000 ->
+ ok
+ end
+ end},
+ #{desc => "announce ready (abort)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, abort),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close listen socket",
+ cmd => fun(#{lsock := Sock}) ->
+ socket:close(Sock)
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ AltServerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ {Tester, Sock} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, sock => Sock}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (recv)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv)
+ end},
+ #{desc => "try recv request (with nowait, expect select)",
+ cmd => fun(#{sock := Sock, recv := Recv} = State) ->
+ case Recv(Sock) of
+ {select, SelectInfo} ->
+ {ok, State#{recv_select_info => SelectInfo}};
+ {ok, X} ->
+ {error, {unexpected_select_info, X}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv_select)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_select),
+ ok
+ end},
+ #{desc => "await abort message",
+ cmd => fun(#{sock := Sock,
+ recv_select_info := {select_info, _, Ref}} = State) ->
+ receive
+ {'$socket', Sock, select, Ref} ->
+ {error, {unexpected_select, Ref}};
+ {'$socket', Sock, abort, {Ref, closed}} ->
+ {ok, maps:remove(sock, State)}
+ after 5000 ->
+ ?SEV_EPRINT("message queue: ~p", [mq()]),
+ {error, timeout}
+ end
+ end},
+ #{desc => "announce ready (abort)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, abort),
+ ok
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ ?SEV_IPRINT("terminating"),
+ State1 = maps:remove(recv_select_info, State),
+ State2 = maps:remove(tester, State1),
+ State3 = maps:remove(sock, State2),
+ {ok, State3};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ ClientSeq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ {Tester, Port} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, server_port => Port}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** The init part ***
+ #{desc => "which server (local) address",
+ cmd => fun(#{domain := Domain, server_port := Port} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ SSA = LSA#{port => Port},
+ {ok, State#{local_sa => LSA, server_sa => SSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{sock := Sock, local_sa := LSA} = _State) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% *** The actual test ***
+ #{desc => "await continue (connect)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, connect)
+ end},
+ #{desc => "connect to server",
+ cmd => fun(#{sock := Sock, server_sa := SSA}) ->
+ socket:connect(Sock, SSA)
+ end},
+ #{desc => "announce ready (connect)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, connect),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock}) ->
+ socket:close(Sock)
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor alt-server 1",
+ cmd => fun(#{alt_server1 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor alt-server 2",
+ cmd => fun(#{alt_server2 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = State) ->
+ {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{server_port => Port}}
+ end},
+
+ %% Start the client
+ #{desc => "order client start",
+ cmd => fun(#{client := Pid, server_port := Port} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Port),
+ ok
+ end},
+ #{desc => "await client ready (init)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client, init)
+ end},
+
+ #{desc => "order server to continue (with accept)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, accept),
+ ok
+ end},
+ #{desc => "order client to continue (connect)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, connect),
+ ok
+ end},
+ #{desc => "await client ready (connect)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, client, connect)
+ end},
+ #{desc => "await server ready (accept)",
+ cmd => fun(#{server := Pid} = State) ->
+ {ok, Sock} = ?SEV_AWAIT_READY(Pid, server, accept),
+ {ok, State#{sock => Sock}}
+ end},
+
+ %% Start the alt server 1
+ #{desc => "order alt-server 1 start",
+ cmd => fun(#{alt_server1 := Pid, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Sock),
+ ok
+ end},
+ #{desc => "await alt-server 1 ready (init)",
+ cmd => fun(#{alt_server1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, alt_server1, init)
+ end},
+
+ %% Start the alt server 2
+ #{desc => "order alt-server 2 start",
+ cmd => fun(#{alt_server2 := Pid, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Sock),
+ ok
+ end},
+ #{desc => "await alt-server 2 ready (init)",
+ cmd => fun(#{alt_server2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, alt_server2, init)
+ end},
+
+
+ %% *** The actual test ***
+ #{desc => "order server continue (recv)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ #{desc => "await server ready (recv select)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, server, recv_select)
+ end},
+
+ #{desc => "order alt-server 1 continue (recv)",
+ cmd => fun(#{alt_server1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ #{desc => "await alt-server 1 ready (recv select)",
+ cmd => fun(#{alt_server1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, alt_server1, recv_select)
+ end},
+
+ #{desc => "order alt-server 2 continue (recv)",
+ cmd => fun(#{alt_server2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ #{desc => "await alt-server 2 ready (recv select)",
+ cmd => fun(#{alt_server2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, alt_server2, recv_select)
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "close the socket",
+ cmd => fun(#{sock := Sock} = _State) ->
+ socket:close(Sock)
+ end},
+
+ #{desc => "await server ready (abort)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, server, abort)
+ end},
+ #{desc => "await alt-server 1 ready (abort)",
+ cmd => fun(#{alt_server1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, alt_server1, abort)
+ end},
+ #{desc => "await alt-server 2 ready (abort)",
+ cmd => fun(#{alt_server2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, alt_server2, abort)
+ end},
+
+ %% Terminations
+ #{desc => "order client to terminate",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await client termination",
+ cmd => fun(#{client := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(client, State),
+ {ok, State1}
+ end},
+
+ #{desc => "order alt-server 2 to terminate",
+ cmd => fun(#{alt_server2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await alt-server 2 termination",
+ cmd => fun(#{alt_server2 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(alt_server2, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "order alt-server 1 to terminate",
+ cmd => fun(#{alt_server1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await alt-server 1 termination",
+ cmd => fun(#{alt_server1 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(alt_server1, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(server, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start server evaluator"),
+ Server = ?SEV_START("server", ServerSeq, InitState),
+
+ i("start alt-server 1 evaluator"),
+ AltServer1 = ?SEV_START("alt_server1", AltServerSeq, InitState),
+
+ i("start alt-server 2 evaluator"),
+ AltServer2 = ?SEV_START("alt_server2", AltServerSeq, InitState),
+
+ i("start client evaluator"),
+ Client = ?SEV_START("client", ClientSeq, InitState),
+
+ i("start tester evaluator"),
+ TesterInitState = #{server => Server#ev.pid,
+ alt_server1 => AltServer1#ev.pid,
+ alt_server2 => AltServer2#ev.pid,
+ client => Client#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator(s)"),
+ ok = ?SEV_AWAIT_FINISH([Server, AltServer1, AltServer2, Client, Tester]).
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% %%
+%% API OPTIONS %%
+%% %%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Perform some simple getopt and setopt with the level = otp options
+api_opt_simple_otp_options(suite) ->
+ [];
+api_opt_simple_otp_options(doc) ->
+ [];
+api_opt_simple_otp_options(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_simple_otp_options,
+ fun() -> api_opt_simple_otp_options() end).
+
+api_opt_simple_otp_options() ->
+ Get = fun(S, Key) ->
+ socket:getopt(S, otp, Key)
+ end,
+ Set = fun(S, Key, Val) ->
+ socket:setopt(S, otp, Key, Val)
+ end,
+
+ Seq =
+ [
+ %% *** Init part ***
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Protocol} = State) ->
+ Sock = sock_open(Domain, Type, Protocol),
+ {ok, State#{sock => Sock}}
+ end},
+ #{desc => "create dummy process",
+ cmd => fun(State) ->
+ Pid = spawn_link(fun() ->
+ put(sname, "dummy"),
+ receive
+ die ->
+ exit(normal)
+ end
+ end),
+ {ok, State#{dummy => Pid}}
+ end},
+
+ %% *** Check iow part ***
+ #{desc => "get iow",
+ cmd => fun(#{sock := Sock} = State) ->
+ case Get(Sock, iow) of
+ {ok, IOW} when is_boolean(IOW) ->
+ {ok, State#{iow => IOW}};
+ {ok, InvalidIOW} ->
+ {error, {invalid, InvalidIOW}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% #{desc => "enable debug",
+ %% cmd => fun(#{sock := Sock}) ->
+ %% ok = socket:setopt(Sock, otp, debug, true)
+ %% end},
+
+ #{desc => "set (new) iow",
+ cmd => fun(#{sock := Sock, iow := OldIOW} = State) ->
+ NewIOW = not OldIOW,
+ case Set(Sock, iow, NewIOW) of
+ ok ->
+ {ok, State#{iow => NewIOW}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "get (new) iow",
+ cmd => fun(#{sock := Sock, iow := IOW}) ->
+ case Get(Sock, iow) of
+ {ok, IOW} ->
+ ok;
+ {ok, InvalidIOW} ->
+ {error, {invalid, InvalidIOW}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** Check rcvbuf part ***
+ #{desc => "get rcvbuf",
+ cmd => fun(#{sock := Sock} = State) ->
+ case Get(Sock, rcvbuf) of
+ {ok, RcvBuf} when is_integer(RcvBuf) ->
+ {ok, State#{rcvbuf => RcvBuf}};
+ {ok, {N, RcvBuf} = V} when is_integer(N) andalso
+ is_integer(RcvBuf) ->
+ {ok, State#{rcvbuf => V}};
+ {ok, InvalidRcvBuf} ->
+ {error, {invalid, InvalidRcvBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "set (new) rcvbuf",
+ cmd => fun(#{sock := Sock, rcvbuf := {OldN, OldRcvBuf}} = State) ->
+ NewRcvBuf = {OldN+2, OldRcvBuf + 1024},
+ case Set(Sock, rcvbuf, NewRcvBuf) of
+ ok ->
+ {ok, State#{rcvbuf => NewRcvBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end;
+ (#{sock := Sock, rcvbuf := OldRcvBuf} = State) when is_integer(OldRcvBuf) ->
+ NewRcvBuf = 2 * OldRcvBuf,
+ case Set(Sock, rcvbuf, NewRcvBuf) of
+ ok ->
+ {ok, State#{rcvbuf => NewRcvBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end;
+ (#{sock := Sock, rcvbuf := OldRcvBuf,
+ type := stream,
+ protocol := tcp} = State) when is_integer(OldRcvBuf) ->
+ NewRcvBuf = {2, OldRcvBuf},
+ case Set(Sock, rcvbuf, NewRcvBuf) of
+ ok ->
+ {ok, State#{rcvbuf => NewRcvBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "get (new) rcvbuf",
+ cmd => fun(#{sock := Sock, rcvbuf := RcvBuf}) ->
+ case Get(Sock, rcvbuf) of
+ {ok, RcvBuf} ->
+ ok;
+ {ok, InvalidRcvBuf} ->
+ {error, {invalid, InvalidRcvBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** Check rcvctrlbuf part ***
+ #{desc => "get rcvctrlbuf",
+ cmd => fun(#{sock := Sock} = State) ->
+ case Get(Sock, rcvctrlbuf) of
+ {ok, RcvCtrlBuf} when is_integer(RcvCtrlBuf) ->
+ {ok, State#{rcvctrlbuf => RcvCtrlBuf}};
+ {ok, InvalidRcvCtrlBuf} ->
+ {error, {invalid, InvalidRcvCtrlBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "set (new) rcvctrlbuf",
+ cmd => fun(#{sock := Sock, rcvctrlbuf := OldRcvCtrlBuf} = State) ->
+ NewRcvCtrlBuf = 2 * OldRcvCtrlBuf,
+ case Set(Sock, rcvctrlbuf, NewRcvCtrlBuf) of
+ ok ->
+ {ok, State#{rcvctrlbuf => NewRcvCtrlBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "get (new) rcvctrlbuf",
+ cmd => fun(#{sock := Sock, rcvctrlbuf := RcvCtrlBuf}) ->
+ case Get(Sock, rcvctrlbuf) of
+ {ok, RcvCtrlBuf} ->
+ ok;
+ {ok, InvalidRcvCtrlBuf} ->
+ {error, {invalid, InvalidRcvCtrlBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ %% *** Check rcvctrlbuf part ***
+ #{desc => "get rcvctrlbuf",
+ cmd => fun(#{sock := Sock} = State) ->
+ case Get(Sock, rcvctrlbuf) of
+ {ok, RcvCtrlBuf} when is_integer(RcvCtrlBuf) ->
+ {ok, State#{rcvctrlbuf => RcvCtrlBuf}};
+ {ok, InvalidRcvCtrlBuf} ->
+ {error, {invalid, InvalidRcvCtrlBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "set (new) rcvctrlbuf",
+ cmd => fun(#{sock := Sock, rcvctrlbuf := OldRcvCtrlBuf} = State) ->
+ NewRcvCtrlBuf = 2 * OldRcvCtrlBuf,
+ case Set(Sock, rcvctrlbuf, NewRcvCtrlBuf) of
+ ok ->
+ {ok, State#{rcvctrlbuf => NewRcvCtrlBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "get (new) rcvctrlbuf",
+ cmd => fun(#{sock := Sock, rcvctrlbuf := RcvCtrlBuf}) ->
+ case Get(Sock, rcvctrlbuf) of
+ {ok, RcvCtrlBuf} ->
+ ok;
+ {ok, InvalidRcvCtrlBuf} ->
+ {error, {invalid, InvalidRcvCtrlBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ %% *** Check sndctrlbuf part ***
+ #{desc => "get sndctrlbuf",
+ cmd => fun(#{sock := Sock} = State) ->
+ case Get(Sock, sndctrlbuf) of
+ {ok, SndCtrlBuf} when is_integer(SndCtrlBuf) ->
+ {ok, State#{sndctrlbuf => SndCtrlBuf}};
+ {ok, InvalidSndCtrlBuf} ->
+ {error, {invalid, InvalidSndCtrlBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "set (new) sndctrlbuf",
+ cmd => fun(#{sock := Sock, sndctrlbuf := OldSndCtrlBuf} = State) ->
+ NewSndCtrlBuf = 2 * OldSndCtrlBuf,
+ case Set(Sock, sndctrlbuf, NewSndCtrlBuf) of
+ ok ->
+ {ok, State#{sndctrlbuf => NewSndCtrlBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "get (new) sndctrlbuf",
+ cmd => fun(#{sock := Sock, sndctrlbuf := SndCtrlBuf}) ->
+ case Get(Sock, sndctrlbuf) of
+ {ok, SndCtrlBuf} ->
+ ok;
+ {ok, InvalidSndCtrlBuf} ->
+ {error, {invalid, InvalidSndCtrlBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** Check controlling-process part ***
+ #{desc => "verify self as controlling-process",
+ cmd => fun(#{sock := Sock}) ->
+ Self = self(),
+ case Get(Sock, controlling_process) of
+ {ok, Self} ->
+ ok;
+ {ok, InvalidPid} ->
+ {error, {invalid, InvalidPid}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "set dummy as controlling-process",
+ cmd => fun(#{sock := Sock, dummy := Dummy}) ->
+ Set(Sock, controlling_process, Dummy)
+ end},
+ #{desc => "verify dummy as controlling-process",
+ cmd => fun(#{sock := Sock, dummy := Dummy}) ->
+ case Get(Sock, controlling_process) of
+ {ok, Dummy} ->
+ ok;
+ {ok, InvalidPid} ->
+ {error, {invalid, InvalidPid}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ #{desc => "finish",
+ cmd => fun(_) ->
+ {ok, normal}
+ end}
+ ],
+
+ i("start tcp (stream) evaluator"),
+ InitState1 = #{domain => inet, type => stream, protocol => tcp},
+ Tester1 = ?SEV_START("tcp-tester", Seq, InitState1),
+ i("await tcp evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Tester1]),
+
+ i("start udp (dgram) socket"),
+ InitState2 = #{domain => inet, type => dgram, protocol => udp},
+ Tester2 = ?SEV_START("udp-tester", Seq, InitState2),
+ i("await udp evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Tester2]).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Perform some simple getopt and setopt otp meta option
+api_opt_simple_otp_meta_option(suite) ->
+ [];
+api_opt_simple_otp_meta_option(doc) ->
+ [];
+api_opt_simple_otp_meta_option(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_simple_otp_meta_option,
+ fun() -> api_opt_simple_otp_meta_option() end).
+
+api_opt_simple_otp_meta_option() ->
+ Get = fun(S) ->
+ socket:getopt(S, otp, meta)
+ end,
+ Set = fun(S, Val) ->
+ socket:setopt(S, otp, meta, Val)
+ end,
+
+ MainSeq =
+ [
+ #{desc => "monitor helper",
+ cmd => fun(#{helper := Pid}) ->
+ _ = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Protocol} = State) ->
+ Sock = sock_open(Domain, Type, Protocol),
+ {ok, State#{sock => Sock}}
+ end},
+
+ #{desc => "get default",
+ cmd => fun(#{sock := Sock}) ->
+ case Get(Sock) of
+ {ok, undefined} ->
+ ok;
+ {ok, Invalid} ->
+ {error, {invalid, Invalid}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "set value",
+ cmd => fun(#{sock := Sock} = State) ->
+ Value = make_ref(),
+ case Set(Sock, Value) of
+ ok ->
+ {ok, State#{value => Value}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "get value",
+ cmd => fun(#{sock := Sock, value := Value}) ->
+ case Get(Sock) of
+ {ok, Value} ->
+ ok;
+ {ok, Invalid} ->
+ {error, {invalid, Invalid}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "set complex value",
+ cmd => fun(#{sock := Sock} = State) ->
+ Value =
+ #{a => 1,
+ b => {2, 3},
+ c => make_ref(),
+ d => self(),
+ e => State},
+ case Set(Sock, Value) of
+ ok ->
+ {ok, State#{value := Value}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "get complex value",
+ cmd => fun(#{sock := Sock, value := Value}) ->
+ case Get(Sock) of
+ {ok, Value} ->
+ ok;
+ {ok, Invalid} ->
+ {error, {invalid, Invalid}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "start helper",
+ cmd => fun(#{helper := Pid, sock := Sock, value := Value}) ->
+ ?SEV_ANNOUNCE_START(Pid, {Sock, Value}),
+ ok
+ end},
+
+ #{desc => "wait for helper ready",
+ cmd => fun(#{helper := Pid}) ->
+ ?SEV_AWAIT_READY(Pid, helper, test)
+ end},
+
+ #{desc => "socket close",
+ cmd => fun(#{sock := Sock}) ->
+ socket:close(Sock)
+ end},
+
+ ?SEV_FINISH_NORMAL],
+
+ HelperSeq =
+ [#{desc => "await start",
+ cmd => fun (State) ->
+ {Main, {Sock, Value}} = ?SEV_AWAIT_START(),
+ {ok, State#{main => Main,
+ sock => Sock,
+ value => Value}}
+ end},
+ #{desc => "monitor main",
+ cmd => fun(#{main := Main}) ->
+ _ = erlang:monitor(process, Main),
+ ok
+ end}
+
+ #{desc => "get value",
+ cmd => fun(#{sock := Sock, value := Value}) ->
+ case Get(Sock) of
+ {ok, Value} ->
+ ok;
+ {ok, Invalid} ->
+ {error, {invalid, Invalid}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "set and fail",
+ cmd => fun(#{sock := Sock}) ->
+ Value = self(),
+ case Set(Sock, Value) of
+ ok ->
+ {error, only_owner_may_set};
+ {error, not_owner} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "announce ready (test)",
+ cmd => fun(#{main := Main}) ->
+ ?SEV_ANNOUNCE_READY(Main, test),
+ ok
+ end},
+
+ ?SEV_FINISH_NORMAL],
+
+
+ i("start tcp helper evaluator"),
+ Helper = ?SEV_START("tcp-helper", HelperSeq, #{}),
+
+ i("start tcp main evaluator"),
+ MainState = #{domain => inet, type => stream, protocol => tcp,
+ helper => Helper#ev.pid},
+ Main = ?SEV_START("tcp-main", MainSeq, MainState),
+
+ i("await tcp evaluators"),
+ ok = ?SEV_AWAIT_FINISH([Helper, Main]).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Perform some simple operations with the rcvbuf otp option
+%% The operations we test here are only for type = stream and
+%% protocol = tcp.
+api_opt_simple_otp_rcvbuf_option(suite) ->
+ [];
+api_opt_simple_otp_rcvbuf_option(doc) ->
+ [];
+api_opt_simple_otp_rcvbuf_option(_Config) when is_list(_Config) ->
+ ?TT(?SECS(15)),
+ tc_try(api_opt_simple_otp_rcvbuf_option,
+ fun() -> api_opt_simple_otp_rcvbuf_option() end).
+
+api_opt_simple_otp_rcvbuf_option() ->
+ Get = fun(S) ->
+ socket:getopt(S, otp, rcvbuf)
+ end,
+ Set = fun(S, Val) ->
+ socket:setopt(S, otp, rcvbuf, Val)
+ end,
+
+ ServerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "create listen socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{lsock := LSock, local_sa := LSA} = State) ->
+ case sock_bind(LSock, LSA) of
+ {ok, Port} ->
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester,
+ local_sa := LocalSA,
+ lport := Port}) ->
+ ServerSA = LocalSA#{port => Port},
+ ?SEV_ANNOUNCE_READY(Tester, init, ServerSA),
+ ok
+ end},
+
+
+ %% *** The actual test part ***
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "attempt to accept",
+ cmd => fun(#{lsock := LSock} = State) ->
+ case socket:accept(LSock) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept),
+ ok
+ end},
+
+ %% Recv with default size for (otp) rcvbuf
+ #{desc => "await continue (recv initial)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_CONTINUE(Tester, tester, recv) of
+ {ok, MsgSz} ->
+ ?SEV_IPRINT("MsgSz: ~p", [MsgSz]),
+ {ok, State#{msg_sz => MsgSz}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "attempt to recv",
+ cmd => fun(#{sock := Sock, msg_sz := MsgSz} = _State) ->
+ ?SEV_IPRINT("try recv ~w bytes when rcvbuf is ~s",
+ [MsgSz,
+ case Get(Sock) of
+ {ok, RcvBuf} -> f("~w", [RcvBuf]);
+ {error, _} -> "-"
+ end]),
+ case socket:recv(Sock) of
+ {ok, Data} when (size(Data) =:= MsgSz) ->
+ ok;
+ {ok, Data} ->
+ {error, {invalid_msg_sz, MsgSz, size(Data)}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv initial)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv),
+ ok
+ end},
+
+ %% Recv with new size (1) for (otp) rcvbuf
+ #{desc => "await continue (recv 1)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_CONTINUE(Tester, tester, recv) of
+ {ok, NewRcvBuf} ->
+ ?SEV_IPRINT("set new rcvbuf: ~p", [NewRcvBuf]),
+ {ok, State#{rcvbuf => NewRcvBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "attempt to setopt rcvbuf",
+ cmd => fun(#{sock := Sock, rcvbuf := NewRcvBuf} = _State) ->
+ case Set(Sock, NewRcvBuf) of
+ ok ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "attempt to recv",
+ cmd => fun(#{sock := Sock, msg_sz := MsgSz} = _State) ->
+ case socket:recv(Sock) of
+ {ok, Data} when (size(Data) =:= MsgSz) ->
+ ok;
+ {ok, Data} ->
+ {error, {invalid_msg_sz, MsgSz, size(Data)}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv 1)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv),
+ ok
+ end},
+
+ %% Recv with new size (2) for (otp) rcvbuf
+ #{desc => "await continue (recv 2)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_CONTINUE(Tester, tester, recv) of
+ {ok, NewRcvBuf} ->
+ ?SEV_IPRINT("set new rcvbuf: ~p", [NewRcvBuf]),
+ {ok, State#{rcvbuf => NewRcvBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "attempt to setopt rcvbuf",
+ cmd => fun(#{sock := Sock, rcvbuf := NewRcvBuf} = _State) ->
+ case Set(Sock, NewRcvBuf) of
+ ok ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "attempt to recv",
+ cmd => fun(#{sock := Sock, msg_sz := MsgSz} = _State) ->
+ case socket:recv(Sock) of
+ {ok, Data} when (size(Data) =:= MsgSz) ->
+ ok;
+ {ok, Data} ->
+ {error, {invalid_msg_sz, MsgSz, size(Data)}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv 2)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv),
+ ok
+ end},
+
+ %% Recv with new size (3) for (otp) rcvbuf
+ #{desc => "await continue (recv 3, truncated)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_CONTINUE(Tester, tester, recv) of
+ {ok, {ExpSz, NewRcvBuf}} ->
+ {ok, State#{msg_sz => ExpSz,
+ rcvbuf => NewRcvBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "attempt to setopt rcvbuf",
+ cmd => fun(#{sock := Sock, rcvbuf := NewRcvBuf} = _State) ->
+ case Set(Sock, NewRcvBuf) of
+ ok ->
+ ?SEV_IPRINT("set new rcvbuf: ~p", [NewRcvBuf]),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "attempt to recv",
+ cmd => fun(#{sock := Sock, msg_sz := MsgSz} = _State) ->
+ ?SEV_IPRINT("try recv ~w bytes of data", [MsgSz]),
+ case socket:recv(Sock) of
+ {ok, Data} when (size(Data) =:= MsgSz) ->
+ ok;
+ {ok, Data} ->
+ {error, {invalid_msg_sz, MsgSz, size(Data)}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv),
+ ok
+ end},
+
+
+ %% Termination
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket(s)",
+ cmd => fun(#{lsock := LSock, sock := Sock} = State) ->
+ sock_close(Sock),
+ sock_close(LSock),
+ State1 = maps:remove(sock, State),
+ State2 = maps:remove(lport, State1),
+ State3 = maps:remove(lsock, State2),
+ {ok, State3}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ ClientSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ {Tester, ServerSA} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ server_sa => ServerSA}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{sock := Sock, local_sa := LSA} = _State) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (connect)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, connect)
+ end},
+ #{desc => "connect to server",
+ cmd => fun(#{sock := Sock, server_sa := SSA}) ->
+ socket:connect(Sock, SSA)
+ end},
+ #{desc => "announce ready (connect)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, connect),
+ ok
+ end},
+
+ #{desc => "await continue (send initial)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_CONTINUE(Tester, tester, send) of
+ {ok, Data} ->
+ {ok, State#{data => Data}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "send (initial) data to server",
+ cmd => fun(#{sock := Sock, data := Data} = _State) ->
+ ?SEV_IPRINT("try send ~w bytes", [size(Data)]),
+ socket:send(Sock, Data)
+ end},
+ #{desc => "announce ready (send initial)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send),
+ ok
+ end},
+
+ #{desc => "await continue (send 1)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send)
+ end},
+ #{desc => "send (1) data to server",
+ cmd => fun(#{sock := Sock, data := Data}) ->
+ ?SEV_IPRINT("try send ~w bytes", [size(Data)]),
+ socket:send(Sock, Data)
+ end},
+ #{desc => "announce ready (send 1)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send),
+ ok
+ end},
+
+ #{desc => "await continue (send 2)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send)
+ end},
+ #{desc => "send (2) data to server",
+ cmd => fun(#{sock := Sock, data := Data}) ->
+ ?SEV_IPRINT("try send ~w bytes", [size(Data)]),
+ socket:send(Sock, Data)
+ end},
+ #{desc => "announce ready (send 2)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send),
+ ok
+ end},
+
+ #{desc => "await continue (send 3)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send)
+ end},
+ #{desc => "send (3) data to server",
+ cmd => fun(#{sock := Sock, data := Data}) ->
+ ?SEV_IPRINT("try send ~w bytes", [size(Data)]),
+ socket:send(Sock, Data)
+ end},
+ #{desc => "announce ready (send 3)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send),
+ ok
+ end},
+
+
+ %% Termination
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock}) ->
+ socket:close(Sock)
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Server} = _State) ->
+ _MRef = erlang:monitor(process, Server),
+ ok
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Client} = _State) ->
+ _MRef = erlang:monitor(process, Client),
+ ok
+ end},
+ #{desc => "order server start",
+ cmd => fun(#{server := Server}) ->
+ ?SEV_ANNOUNCE_START(Server)
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Server} = State) ->
+ {ok, ServerSA} = ?SEV_AWAIT_READY(Server, server, init),
+ {ok, State#{server_sa => ServerSA}}
+ end},
+ #{desc => "order client start",
+ cmd => fun(#{client := Client,
+ server_sa := ServerSA}) ->
+ ?SEV_ANNOUNCE_START(Client, ServerSA),
+ ok
+ end},
+ #{desc => "await client ready (init)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, init)
+ end},
+
+
+ %% The actual test (connecting)
+ #{desc => "order server accept (accept)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, accept),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order client continue (connect)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, connect),
+ ok
+ end},
+ #{desc => "await client ready (connect)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, connect)
+ end},
+ #{desc => "await server ready (accept)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, accept)
+ end},
+
+ %% The actual test (initial part)
+ #{desc => "order client continue (send initial)",
+ cmd => fun(#{client := Client, data := Data} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send, Data),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order server continue (recv initial)",
+ cmd => fun(#{server := Server, data := Data} = _State) ->
+ ExpMsgSz = size(Data),
+ ?SEV_ANNOUNCE_CONTINUE(Server, recv, ExpMsgSz),
+ ok
+ end},
+ #{desc => "await client ready (send initial)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, client, send,
+ [{server, Server}]) of
+ ok ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "await server ready (recv initial)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Server, client, recv,
+ [{client, Client}]) of
+ ok ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% The actual test (part 1)
+ #{desc => "order client continue (send 1)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order server continue (recv 1)",
+ cmd => fun(#{server := Server, data := Data} = _State) ->
+ MsgSz = size(Data),
+ NewRcvBuf = {2 + (MsgSz div 1024), 1024},
+ ?SEV_ANNOUNCE_CONTINUE(Server, recv, NewRcvBuf),
+ ok
+ end},
+ #{desc => "await client ready (send 1)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, client, send,
+ [{server, Server}]) of
+ ok ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "await server ready (recv 1)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Server, client, recv,
+ [{client, Client}]) of
+ ok ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% The actual test (part 2)
+ #{desc => "order client continue (send 2)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order server continue (recv 2)",
+ cmd => fun(#{server := Server, data := Data} = _State) ->
+ MsgSz = size(Data),
+ NewRcvBuf = {2 + (MsgSz div 2048), 2048},
+ ?SEV_ANNOUNCE_CONTINUE(Server, recv, NewRcvBuf),
+ ok
+ end},
+ #{desc => "await client ready (send 2)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, client, send,
+ [{server, Server}]) of
+ ok ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "await server ready (recv 2)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Server, client, recv,
+ [{client, Client}]) of
+ ok ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ %% The actual test (part 3)
+ #{desc => "order client continue (send 3)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order server continue (recv 3)",
+ cmd => fun(#{server := Server, data := Data} = _State) ->
+ MsgSz = size(Data),
+ BufSz = 2048,
+ N = MsgSz div BufSz - 1,
+ NewRcvBuf = {N, BufSz},
+ ?SEV_ANNOUNCE_CONTINUE(Server, recv,
+ {N*BufSz, NewRcvBuf})
+ end},
+ #{desc => "await client ready (send 3)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, client, send,
+ [{server, Server}]) of
+ ok ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "await server ready (recv 3)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Server, client, recv,
+ [{client, Client}]) of
+ ok ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ %% *** Terminate server ***
+ #{desc => "order client terminate",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await client down",
+ cmd => fun(#{client := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(client, State),
+ {ok, State1}
+ end},
+ #{desc => "order server terminate",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Server),
+ ok
+ end},
+ #{desc => "await server down",
+ cmd => fun(#{server := Server} = State) ->
+ ?SEV_AWAIT_TERMINATION(Server),
+ State1 = maps:remove(server, State),
+ State2 = maps:remove(server_sa, State1),
+ {ok, State2}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ %% Create a data binary of 6*1024 bytes
+ Data = list_to_binary(lists:duplicate(6*4, lists:seq(0, 255))),
+ InitState = #{domain => inet,
+ data => Data},
+
+ i("create server evaluator"),
+ ServerInitState = #{domain => maps:get(domain, InitState)},
+ Server = ?SEV_START("server", ServerSeq, ServerInitState),
+
+ i("create client evaluator"),
+ ClientInitState = #{host => local_host(),
+ domain => maps:get(domain, InitState)},
+ Client = ?SEV_START("client", ClientSeq, ClientInitState),
+
+ i("create tester evaluator"),
+ TesterInitState = InitState#{server => Server#ev.pid,
+ client => Client#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator(s)"),
+ ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Perform some simple getopt and setopt with the level = otp options
+api_opt_simple_otp_controlling_process(suite) ->
+ [];
+api_opt_simple_otp_controlling_process(doc) ->
+ [];
+api_opt_simple_otp_controlling_process(_Config) when is_list(_Config) ->
+ ?TT(?SECS(30)),
+ tc_try(api_opt_simple_otp_controlling_process,
+ fun() -> api_opt_simple_otp_controlling_process() end).
+
+api_opt_simple_otp_controlling_process() ->
+ Get = fun(S, Key) ->
+ socket:getopt(S, otp, Key)
+ end,
+ Set = fun(S, Key, Val) ->
+ socket:setopt(S, otp, Key, Val)
+ end,
+
+ ClientSeq =
+ [
+ %% *** Init part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ {Tester, Sock} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ sock => Sock}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** The actual test ***
+ #{desc => "verify tester as controlling-process",
+ cmd => fun(#{tester := Tester, sock := Sock} = _State) ->
+ case Get(Sock, controlling_process) of
+ {ok, Tester} ->
+ ok;
+ {ok, InvalidPid} ->
+ {error, {invalid, InvalidPid}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "attempt invalid controlling-process transfer (to self)",
+ cmd => fun(#{sock := Sock} = _State) ->
+ case Set(Sock, controlling_process, self()) of
+ {error, not_owner} ->
+ ok;
+ ok ->
+ {error, unexpected_success};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (not owner)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_ANNOUNCE_READY(Tester, not_owner),
+ ok
+ end},
+ #{desc => "await continue (owner)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, owner)
+ end},
+ #{desc => "verify self as controlling-process",
+ cmd => fun(#{sock := Sock} = _State) ->
+ Self = self(),
+ case Get(Sock, controlling_process) of
+ {ok, Self} ->
+ ok;
+ {ok, InvalidPid} ->
+ {error, {invalid, InvalidPid}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "attempt controlling-process transfer to tester",
+ cmd => fun(#{tester := Tester, sock := Sock} = _State) ->
+ Set(Sock, controlling_process, Tester)
+ end},
+ #{desc => "attempt invalid controlling-process transfer (to self)",
+ cmd => fun(#{sock := Sock} = _State) ->
+ case Set(Sock, controlling_process, self()) of
+ {error, not_owner} ->
+ ok;
+ ok ->
+ {error, unexpected_success};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (owner)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_ANNOUNCE_READY(Tester, owner),
+ ok
+
+ end},
+
+ %% *** Termination ***
+ #{desc => "await termination",
+ cmd => fun(#{tester := Tester} = State) ->
+ ?SEV_AWAIT_TERMINATE(Tester, tester),
+ State1 = maps:remove(tester, State),
+ State2 = maps:remove(sock, State1),
+ {ok, State2}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Protocol} = State) ->
+ Sock = sock_open(Domain, Type, Protocol),
+ {ok, State#{sock => Sock}}
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Client} = _State) ->
+ _MRef = erlang:monitor(process, Client),
+ ok
+ end},
+
+ %% *** The actual test ***
+ #{desc => "verify self as controlling-process",
+ cmd => fun(#{sock := Sock} = _State) ->
+ Self = self(),
+ case Get(Sock, controlling_process) of
+ {ok, Self} ->
+ ok;
+ {ok, InvalidPid} ->
+ {error, {invalid, InvalidPid}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order (client) start",
+ cmd => fun(#{client := Client, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_START(Client, Sock),
+ ok
+ end},
+ #{desc => "await (client) ready (not owner)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, not_owner)
+ end},
+ #{desc => "attempt controlling-process transfer to client",
+ cmd => fun(#{client := Client, sock := Sock} = _State) ->
+ Set(Sock, controlling_process, Client)
+ end},
+ #{desc => "verify client as controlling-process",
+ cmd => fun(#{client := Client, sock := Sock} = _State) ->
+ case Get(Sock, controlling_process) of
+ {ok, Client} ->
+ ok;
+ {ok, InvalidPid} ->
+ {error, {invalid, InvalidPid}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "attempt invalid controlling-process transfer (to self)",
+ cmd => fun(#{sock := Sock} = _State) ->
+ case Set(Sock, controlling_process, self()) of
+ {error, not_owner} ->
+ ok;
+ ok ->
+ {error, unexpected_success};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order (client) continue (owner)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, owner),
+ ok
+ end},
+ #{desc => "await (client) ready (2)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, owner),
+ ok
+ end},
+ #{desc => "verify self as controlling-process",
+ cmd => fun(#{sock := Sock} = _State) ->
+ Self = self(),
+ case Get(Sock, controlling_process) of
+ {ok, Self} ->
+ ok;
+ {ok, InvalidPid} ->
+ {error, {invalid, InvalidPid}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** Termination ***
+ #{desc => "order (client) terminate",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await client termination",
+ cmd => fun(#{client := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ {ok, maps:remove(client, State)}
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ sock_close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start tcp (stream) client evaluator"),
+ ClientInitState1 = #{},
+ Client1 = ?SEV_START("tcp-client", ClientSeq, ClientInitState1),
+
+ i("start tcp (stream) tester evaluator"),
+ TesterInitState1 = #{domain => inet,
+ type => stream,
+ protocol => tcp,
+ client => Client1#ev.pid},
+ Tester1 = ?SEV_START("tcp-tester", TesterSeq, TesterInitState1),
+
+ i("await tcp evaluator(s)"),
+ ok = ?SEV_AWAIT_FINISH([Tester1, Client1]),
+
+ i("start udp (dgram) client evaluator"),
+ ClientInitState2 = #{},
+ Client2 = ?SEV_START("udp-client", ClientSeq, ClientInitState2),
+
+ i("start udp (dgram) tester evaluator"),
+ TesterInitState2 = #{domain => inet,
+ type => dgram,
+ protocol => udp,
+ client => Client2#ev.pid},
+ Tester2 = ?SEV_START("udp-tester", TesterSeq, TesterInitState2),
+
+ i("await udp evaluator(s)"),
+ ok = ?SEV_AWAIT_FINISH([Tester2, Client2]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests the socket option acceptconn for UDP.
+%% This should be possible to get but not set.
+
+api_opt_sock_acceptconn_udp(suite) ->
+ [];
+api_opt_sock_acceptconn_udp(doc) ->
+ [];
+api_opt_sock_acceptconn_udp(_Config) when is_list(_Config) ->
+ ?TT(?SECS(30)),
+ tc_try(api_opt_sock_acceptconn_udp,
+ fun() ->
+ has_support_sock_acceptconn()
+ end,
+ fun() -> api_opt_sock_acceptconn_udp() end).
+
+
+
+api_opt_sock_acceptconn_udp() ->
+ Opt = acceptconn,
+ Set = fun(S, Val) ->
+ socket:setopt(S, socket, Opt, Val)
+ end,
+ Get = fun(S) ->
+ socket:getopt(S, socket, Opt)
+ end,
+
+ TesterSeq =
+ [
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, dgram, udp) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "[get] verify socket (before bind)",
+ cmd => fun(#{sock := Sock} = _State) ->
+ case Get(Sock) of
+ {ok, false} ->
+ ?SEV_IPRINT("Expected Success: "
+ "Not accepting connections"),
+ ok;
+ {ok, true} ->
+ ?SEV_EPRINT("Unexpected Success: "
+ "Accepting connections"),
+ {error, {unexpected_success, {Opt, true}}};
+ {error, enoprotoopt = Reason} ->
+ %% On some platforms this is not accepted
+ %% for UDP, so skip this part (UDP).
+ ?SEV_EPRINT("Expected Failure: "
+ "~p => SKIP", [Reason]),
+ (catch socket:close(Sock)),
+ {skip, Reason};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p",
+ [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "[set] verify socket (before bind)",
+ cmd => fun(#{sock := Sock} = _State) ->
+ case Set(Sock, true) of
+ {error, Reason} ->
+ ?SEV_IPRINT("Expected Failure: ~p",
+ [Reason]),
+ ok;
+ ok ->
+ ?SEV_EPRINT("Unexpected Success: "
+ "Set acceptconn (=true)"),
+ {error, unexpected_success}
+ end
+ end},
+
+ #{desc => "bind socket to local address",
+ cmd => fun(#{sock := Sock, local_sa := LSA} = _State) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "[get] verify socket (after bind)",
+ cmd => fun(#{sock := Sock} = _State) ->
+ case Get(Sock) of
+ {ok, false} ->
+ ?SEV_IPRINT("Expected Success: "
+ "Not accepting connections"),
+ ok;
+ {ok, true} ->
+ ?SEV_EPRINT("Unexpected Success: "
+ "Accepting connections"),
+ {error, {unexpected_success, {Opt, true}}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p",
+ [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "[set] verify socket (after bind)",
+ cmd => fun(#{sock := Sock} = _State) ->
+ case Set(Sock, true) of
+ {error, Reason} ->
+ ?SEV_IPRINT("Expected Failure: ~p",
+ [Reason]),
+ ok;
+ ok ->
+ ?SEV_EPRINT("Unexpected Success: "
+ "Set acceptconn (=true)"),
+ {error, unexpected_success}
+ end
+ end},
+
+ %% *** Termination ***
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ Domain = inet,
+
+ i("start tester evaluator"),
+ InitState = #{domain => Domain},
+ Tester = ?SEV_START("tester", TesterSeq, InitState),
+
+ i("await evaluator(s)"),
+ ok = ?SEV_AWAIT_FINISH([Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests the socket option acceptconn for TCP.
+%% This should be possible to get but not set.
+
+api_opt_sock_acceptconn_tcp(suite) ->
+ [];
+api_opt_sock_acceptconn_tcp(doc) ->
+ [];
+api_opt_sock_acceptconn_tcp(_Config) when is_list(_Config) ->
+ ?TT(?SECS(30)),
+ tc_try(api_opt_sock_acceptconn_tcp,
+ fun() ->
+ has_support_sock_acceptconn()
+ end,
+ fun() -> api_opt_sock_acceptconn_tcp() end).
+
+
+
+api_opt_sock_acceptconn_tcp() ->
+ Opt = acceptconn,
+ Set = fun(S, Val) ->
+ socket:setopt(S, socket, Opt, Val)
+ end,
+ Get = fun(S) ->
+ socket:getopt(S, socket, Opt)
+ end,
+
+ TesterSeq =
+ [
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{local_sa => LSA}}
+ end},
+
+ #{desc => "create listen socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "[get] verify listen socket (before bind)",
+ cmd => fun(#{lsock := Sock} = _State) ->
+ case Get(Sock) of
+ {ok, false} ->
+ ?SEV_IPRINT("Expected Success: "
+ "Not accepting connections"),
+ ok;
+ {ok, true} ->
+ ?SEV_EPRINT("Unexpected Success: "
+ "Accepting connections"),
+ {error, {unexpected_success, {Opt, true}}};
+ {error, enoprotoopt = Reason} ->
+ ?SEV_EPRINT("Expected Failure: "
+ "~p => SKIP", [Reason]),
+ (catch socket:close(Sock)),
+ {skip, Reason};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p",
+ [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "[set] verify listen socket (before bind)",
+ cmd => fun(#{lsock := Sock} = _State) ->
+ case Set(Sock, true) of
+ {error, Reason} ->
+ ?SEV_IPRINT("Expected Failure: ~p", [Reason]),
+ ok;
+ ok ->
+ ?SEV_EPRINT("Unexpected Success: "
+ "Set acceptconn (=true)"),
+ {error, unexpected_success}
+ end
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "bind listen socket to local address",
+ cmd => fun(#{lsock := Sock, local_sa := LSA} = State) ->
+ case sock_bind(Sock, LSA) of
+ {ok, Port} ->
+ {ok, State#{server_sa => LSA#{port => Port}}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "[get] verify listen socket (after bind)",
+ cmd => fun(#{lsock := Sock} = _State) ->
+ case Get(Sock) of
+ {ok, false} ->
+ ?SEV_IPRINT("Expected Success: "
+ "Not accepting connections"),
+ ok;
+ {ok, true} ->
+ ?SEV_EPRINT("Unexpected Success: "
+ "Accepting connections"),
+ {error, {unexpected_success, {Opt, true}}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "[set] verify listen socket (after bind)",
+ cmd => fun(#{lsock := Sock} = _State) ->
+ case Set(Sock, true) of
+ {error, Reason} ->
+ ?SEV_IPRINT("Expected Failure: ~p", [Reason]),
+ ok;
+ ok ->
+ ?SEV_EPRINT("Unexpected Success: "
+ "Set acceptconn (=true)"),
+ {error, unexpected_success}
+ end
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "make listen socket accept connections",
+ cmd => fun(#{lsock := Sock} = _State) ->
+ case socket:listen(Sock) of
+ ok ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "[get] verify listen socket (after listen)",
+ cmd => fun(#{lsock := Sock} = _State) ->
+ case Get(Sock) of
+ {ok, true} ->
+ ?SEV_IPRINT("Expected Success: "
+ "Accepting connections"),
+ ok;
+ {ok, false} ->
+ ?SEV_EPRINT("Unexpected Success: "
+ "Not accepting connections"),
+ {error, {unexpected_success, {Opt, false}}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "[set] verify listen socket (after listen)",
+ cmd => fun(#{lsock := Sock} = _State) ->
+ case Set(Sock, false) of
+ {error, Reason} ->
+ ?SEV_IPRINT("Expected Failure: ~p", [Reason]),
+ ok;
+ ok ->
+ ?SEV_EPRINT("Unexpected Success: "
+ "Set acceptconn (=false)"),
+ {error, unexpected_success}
+ end
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "create (connecting) socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{csockc => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "bind connecting socket to local address",
+ cmd => fun(#{csockc := Sock, local_sa := LSA} = _State) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "[get] verify connecting socket (before connect)",
+ cmd => fun(#{csockc := Sock} = _State) ->
+ case Get(Sock) of
+ {ok, false} ->
+ ?SEV_IPRINT("Expected Success: "
+ "Not accepting connections"),
+ ok;
+ {ok, true} ->
+ ?SEV_EPRINT("Unexpected Success: "
+ "Accepting connections"),
+ {error, {unexpected_success, {Opt, true}}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "[set] verify connecting socket (before connect)",
+ cmd => fun(#{csockc := Sock} = _State) ->
+ case Set(Sock, true) of
+ {error, Reason} ->
+ ?SEV_IPRINT("Expected Failure: ~p", [Reason]),
+ ok;
+ ok ->
+ ?SEV_EPRINT("Unexpected Success: "
+ "Set acceptconn (=true)"),
+ {error, unexpected_success}
+ end
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "connect to server",
+ cmd => fun(#{csockc := Sock, server_sa := SSA} = _State) ->
+ case socket:connect(Sock, SSA) of
+ ok ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "accept connection",
+ cmd => fun(#{lsock := Sock} = State) ->
+ case socket:accept(Sock) of
+ {ok, CSock} ->
+ {ok, State#{csocks => CSock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "[get] verify connecting socket (after connect)",
+ cmd => fun(#{csockc := Sock} = _State) ->
+ case Get(Sock) of
+ {ok, false} ->
+ ?SEV_IPRINT("Expected Success: "
+ "Not accepting connections"),
+ ok;
+ {ok, true} ->
+ ?SEV_EPRINT("Unexpected Success: "
+ "Accepting connections"),
+ {error, {unexpected_success, {Opt, true}}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "[set] verify connecting socket (after connect)",
+ cmd => fun(#{csockc := Sock} = _State) ->
+ case Set(Sock, true) of
+ {error, Reason} ->
+ ?SEV_IPRINT("Expected Failure: ~p", [Reason]),
+ ok;
+ ok ->
+ ?SEV_EPRINT("Unexpected Success: "
+ "Set acceptconn (=true)"),
+ {error, unexpected_success}
+ end
+ end},
+
+ #{desc => "[get] verify connected socket",
+ cmd => fun(#{csocks := Sock} = _State) ->
+ case Get(Sock) of
+ {ok, false} ->
+ ?SEV_IPRINT("Expected Success: "
+ "Not accepting connections"),
+ ok;
+ {ok, true} ->
+ ?SEV_EPRINT("Unexpected Success: "
+ "Accepting connections"),
+ {error, {unexpected_success, {Opt, true}}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "[set] verify connected socket",
+ cmd => fun(#{csocks := Sock} = _State) ->
+ case Set(Sock, true) of
+ {error, Reason} ->
+ ?SEV_IPRINT("Expected Failure: ~p", [Reason]),
+ ok;
+ ok ->
+ ?SEV_EPRINT("Unexpected Success: "
+ "Set acceptconn (=true)"),
+ {error, unexpected_success}
+ end
+ end},
+
+ #{desc => "[get] verify listen socket (after connect)",
+ cmd => fun(#{lsock := Sock} = _State) ->
+ case Get(Sock) of
+ {ok, true} ->
+ ?SEV_IPRINT("Expected Success: "
+ "Accepting connections"),
+ ok;
+ {ok, false} ->
+ ?SEV_EPRINT("Unexpected Success: "
+ "Not accepting connections"),
+ {error, {unexpected_success, {Opt, false}}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "[set] verify listen socket (after connect)",
+ cmd => fun(#{lsock := Sock} = _State) ->
+ case Set(Sock, false) of
+ {error, Reason} ->
+ ?SEV_IPRINT("Expected Failure: ~p", [Reason]),
+ ok;
+ ok ->
+ ?SEV_EPRINT("Unexpected Success: "
+ "Set acceptconn (=false)"),
+ {error, unexpected_success}
+ end
+ end},
+
+ %% *** Termination ***
+ #{desc => "close connecting socket(s)",
+ cmd => fun(#{csockc := Sock} = State0) ->
+ socket:close(Sock),
+ State1 = maps:remove(csockc, State0),
+ State2 = maps:remove(csocks, State1), %% Auto-close
+ {ok, maps:remove(csockc, State2)}
+ end},
+ #{desc => "close listen socket",
+ cmd => fun(#{lsock := Sock} = State) ->
+ socket:close(Sock),
+ {ok, maps:remove(lsock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ Domain = inet,
+
+ i("start tester evaluator"),
+ InitState = #{domain => Domain},
+ Tester = ?SEV_START("tester", TesterSeq, InitState),
+
+ i("await evaluator(s)"),
+ ok = ?SEV_AWAIT_FINISH([Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests the socket option acceptfilter. PLACEHOLDER!
+
+api_opt_sock_acceptfilter(suite) ->
+ [];
+api_opt_sock_acceptfilter(doc) ->
+ [];
+api_opt_sock_acceptfilter(_Config) when is_list(_Config) ->
+ ?TT(?SECS(30)),
+ tc_try(api_opt_sock_acceptfilter,
+ fun() -> not_yet_implemented() end,
+ fun() -> ok end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests the socket option bindtodevice.
+%% It has not always been possible to 'get' this option
+%% (atleast on linux).
+
+api_opt_sock_bindtodevice(suite) ->
+ [];
+api_opt_sock_bindtodevice(doc) ->
+ [];
+api_opt_sock_bindtodevice(_Config) when is_list(_Config) ->
+ ?TT(?SECS(30)),
+ tc_try(api_opt_sock_bindtodevice,
+ fun() -> has_support_sock_bindtodevice() end,
+ fun() -> api_opt_sock_bindtodevice() end).
+
+
+api_opt_sock_bindtodevice() ->
+ Opt = bindtodevice,
+ Set = fun(S, Val) ->
+ socket:setopt(S, socket, Opt, Val)
+ end,
+ Get = fun(S) ->
+ socket:getopt(S, socket, Opt)
+ end,
+
+ TesterSeq =
+ [
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ case ?LIB:which_local_host_info(Domain) of
+ {ok, #{name := Name, addr := Addr}} ->
+ ?SEV_IPRINT("local host info (~p): "
+ "~n Name: ~p"
+ "~n Addr: ~p",
+ [Domain, Name, Addr]),
+ LSA = #{family => Domain,
+ addr => Addr},
+ {ok, State#{dev => Name,
+ local_sa => LSA}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "create UDP socket 1",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, dgram, udp) of
+ {ok, Sock} ->
+ {ok, State#{usock1 => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "create UDP socket 2",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, dgram, udp) of
+ {ok, Sock} ->
+ {ok, State#{usock2 => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "create TCP socket 1",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{tsock1 => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "create TCP socket 2",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{tsock2 => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "[get] verify UDP socket 1 (before bindtodevice)",
+ cmd => fun(#{usock1 := Sock} = _State) ->
+ case Get(Sock) of
+ {ok, Dev} ->
+ ?SEV_IPRINT("Expected Success: ~p", [Dev]),
+ ok;
+ {error, enoprotoopt = Reason} ->
+ ?SEV_EPRINT("Unexpected Failure: ~p => SKIP",
+ [Reason]),
+ {skip, Reason};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p",
+ [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "[get] verify UDP socket 2 (before bind)",
+ cmd => fun(#{usock2 := Sock} = _State) ->
+ case Get(Sock) of
+ {ok, Dev} ->
+ ?SEV_IPRINT("Expected Success: ~p", [Dev]),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "[get] verify TCP socket 1 (before bindtodevice)",
+ cmd => fun(#{tsock1 := Sock} = _State) ->
+ case Get(Sock) of
+ {ok, Dev} ->
+ ?SEV_IPRINT("Expected Success: ~p", [Dev]),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "[get] verify TCP socket 2 (before bind)",
+ cmd => fun(#{tsock2 := Sock} = _State) ->
+ case Get(Sock) of
+ {ok, Dev} ->
+ ?SEV_IPRINT("Expected Success: ~p", [Dev]),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "Bind UDP socket 1 to device",
+ cmd => fun(#{usock1 := Sock, dev := Dev} = State) ->
+ case Set(Sock, Dev) of
+ ok ->
+ ?SEV_IPRINT("Expected Success"),
+ ok;
+ {error, eperm = Reason} ->
+ ?SEV_IPRINT("Expected Failure: ~p", [Reason]),
+ (catch socket:close(Sock)),
+ {ok, State#{usock1 => skip}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "Bind UDP socket 2 to local address",
+ cmd => fun(#{usock2 := Sock, local_sa := LSA} = _State) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ?SEV_IPRINT("Expected Success"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "Bind TCP socket 1 to device",
+ cmd => fun(#{usock1 := USock1,
+ tsock1 := Sock, dev := Dev} = State) ->
+ case Set(Sock, Dev) of
+ ok ->
+ ?SEV_IPRINT("Expected Success"),
+ ok;
+ {error, eperm = Reason} when (USock1 =:= skip) ->
+ ?SEV_IPRINT("Expected Failure: ~p", [Reason]),
+ {skip, Reason};
+ {error, eperm = Reason} ->
+ ?SEV_IPRINT("Expected Failure: ~p", [Reason]),
+ (catch socket:close(Sock)),
+ {ok, State#{tsock1 => skip}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "Bind TCP socket 2 to local address",
+ cmd => fun(#{tsock2 := Sock, local_sa := LSA} = _State) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ?SEV_IPRINT("Expected Success"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "[get] verify UDP socket 1 (after bindtodevice)",
+ cmd => fun(#{usock1 := skip} = _State) ->
+ ?SEV_IPRINT("SKIP'ed (previous eperm)"),
+ ok;
+ (#{usock1 := Sock} = _State) ->
+ case Get(Sock) of
+ {ok, Dev} ->
+ ?SEV_IPRINT("Expected Success: ~p", [Dev]),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "[get] verify UDP socket 2 (after bind)",
+ cmd => fun(#{usock2 := Sock} = _State) ->
+ case Get(Sock) of
+ {ok, Dev} ->
+ ?SEV_IPRINT("Expected Success: ~p", [Dev]),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "[get] verify TCP socket 1 (after bindtodevice)",
+ cmd => fun(#{tsock1 := skip} = _State) ->
+ ?SEV_IPRINT("SKIP'ed (previous eperm)"),
+ ok;
+ (#{tsock1 := Sock} = _State) ->
+ case Get(Sock) of
+ {ok, Dev} ->
+ ?SEV_IPRINT("Expected Success: ~p", [Dev]),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "[get] verify TCP socket 2 (after bind)",
+ cmd => fun(#{tsock2 := Sock} = _State) ->
+ case Get(Sock) of
+ {ok, Dev} ->
+ ?SEV_IPRINT("Expected Success: ~p", [Dev]),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ %% *** Termination ***
+ #{desc => "close UDP socket 1",
+ cmd => fun(#{usock1 := skip} = State) ->
+ ?SEV_IPRINT("SKIP'ed (already closed)"),
+ {ok, maps:remove(usock1, State)};
+ (#{usock1 := Sock} = State) ->
+ socket:close(Sock),
+ {ok, maps:remove(usock1, State)}
+ end},
+ #{desc => "close UDP socket 2",
+ cmd => fun(#{usock2 := Sock} = State) ->
+ socket:close(Sock),
+ {ok, maps:remove(usock2, State)}
+ end},
+ #{desc => "close TCP socket 1",
+ cmd => fun(#{tsock1 := skip} = State) ->
+ ?SEV_IPRINT("SKIP'ed (already closed)"),
+ {ok, maps:remove(tsock1, State)};
+ (#{tsock1 := Sock} = State) ->
+ socket:close(Sock),
+ {ok, maps:remove(tsock1, State)}
+ end},
+ #{desc => "close TCP socket 2",
+ cmd => fun(#{tsock2 := Sock} = State) ->
+ socket:close(Sock),
+ {ok, maps:remove(tsock2, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ Domain = inet,
+
+ i("start tester evaluator"),
+ InitState = #{domain => Domain},
+ Tester = ?SEV_START("tester", TesterSeq, InitState),
+
+ i("await evaluator(s)"),
+ ok = ?SEV_AWAIT_FINISH([Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests the socket option broadcast.
+%% Make it possible for datagram sockets to send packets to a broadcast
+%% address (IPv4 only).
+
+api_opt_sock_broadcast(suite) ->
+ [];
+api_opt_sock_broadcast(doc) ->
+ [];
+api_opt_sock_broadcast(_Config) when is_list(_Config) ->
+ ?TT(?SECS(30)),
+ tc_try(api_opt_sock_broadcast,
+ fun() -> has_support_sock_broadcast() end,
+ fun() -> api_opt_sock_broadcast() end).
+
+
+api_opt_sock_broadcast() ->
+ Opt = broadcast,
+ Set = fun(S, Val) when is_boolean(Val) ->
+ socket:setopt(S, socket, Opt, Val)
+ end,
+ Get = fun(S) ->
+ socket:getopt(S, socket, Opt)
+ end,
+
+ TesterSeq =
+ [
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ case ?LIB:which_local_host_info(Domain) of
+ {ok, #{name := Name,
+ addr := Addr,
+ broadaddr := BAddr}} ->
+ ?SEV_IPRINT("local host info: "
+ "~n Name: ~p"
+ "~n Addr: ~p"
+ "~n Broadcast Addr: ~p",
+ [Name, Addr, BAddr]),
+ LSA = #{family => Domain,
+ addr => Addr},
+ BSA = #{family => Domain,
+ addr => BAddr},
+ {ok, State#{lsa => LSA,
+ bsa => BSA}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "[socket 1] create UDP socket (listening 1)",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, dgram, udp) of
+ {ok, Sock} ->
+ {ok, State#{sock1 => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "[socket 1] Bind UDP socket (to limited broadcast address)",
+ cmd => fun(#{sock1 := Sock} = State) ->
+ BSA = #{family => inet,
+ addr => broadcast},
+ ?SEV_IPRINT("Try bind (socket 1) to: "
+ "~n ~p", [BSA]),
+ case socket:bind(Sock, BSA) of
+ {ok, Port} ->
+ ?SEV_IPRINT("Expected Success (bound): ~p",
+ [Port]),
+ {ok, State#{sa1 => BSA#{port => Port}}};
+ {error, eaddrnotavail = Reason} ->
+ ?SEV_IPRINT("~p => "
+ "SKIP limited broadcast test",
+ [Reason]),
+ {ok, State#{sa1 => skip}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p",
+ [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "[socket 1] UDP socket sockname",
+ cmd => fun(#{sa1 := skip} = _State) ->
+ ?SEV_IPRINT("SKIP limited broadcast test"),
+ ok;
+ (#{sock1 := Sock} = _State) ->
+ case socket:sockname(Sock) of
+ {ok, SA} ->
+ ?SEV_IPRINT("SA: ~p", [SA]),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "[socket 2] create UDP socket (listening 2)",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, dgram, udp) of
+ {ok, Sock} ->
+ {ok, State#{sock2 => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "[socket 2] Bind UDP socket (to subnet-directed broadcast address)",
+ cmd => fun(#{sock2 := Sock,
+ bsa := BSA} = State) ->
+ ?SEV_IPRINT("Try bind (socket 1) to: "
+ "~n ~p", [BSA]),
+ case socket:bind(Sock, BSA) of
+ {ok, Port} ->
+ ?SEV_IPRINT("Expected Success (bound): ~p",
+ [Port]),
+ {ok, State#{sa2 => BSA#{port => Port}}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p",
+ [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "[socket 2] UDP socket sockname",
+ cmd => fun(#{sock2 := Sock} = _State) ->
+ case socket:sockname(Sock) of
+ {ok, SA} ->
+ ?SEV_IPRINT("SA: ~p", [SA]),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "[socket 3] create UDP socket (sender)",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, dgram, udp) of
+ {ok, Sock} ->
+ {ok, State#{sock3 => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "[socket 3][get] verify UDP socket (before bind and set)",
+ cmd => fun(#{sock3 := Sock} = _State) ->
+ case Get(Sock) of
+ {ok, false} ->
+ ?SEV_IPRINT("Expected Success: "
+ "broadcast not allowed"),
+ ok;
+ {ok, true} ->
+ ?SEV_IPRINT("Unexpected Success result: "
+ "broadcast already allowed"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p",
+ [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "[socket 3] Try make broadcast allowed",
+ cmd => fun(#{sock3 := Sock} = _State) ->
+ case Set(Sock, true) of
+ ok ->
+ ?SEV_IPRINT("Expected Success: "
+ "broadcast now allowed"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p",
+ [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "[socket 3] verify UDP socket broadcast allowed",
+ cmd => fun(#{sock3 := Sock} = _State) ->
+ case Get(Sock) of
+ {ok, true} ->
+ ?SEV_IPRINT("Expected Success: "
+ "broadcast allowed"),
+ ok;
+ {ok, false} ->
+ ?SEV_IPRINT("Unexpected Success result: "
+ "broadcast *not* allowed"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p",
+ [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "[socket 3] Bind UDP socket (to local address)",
+ cmd => fun(#{sock3 := Sock, lsa := LSA} = State) ->
+ ?SEV_IPRINT("Try bind (socket 2) to: "
+ "~n ~p", [LSA]),
+ case socket:bind(Sock, LSA) of
+ {ok, Port} ->
+ ?SEV_IPRINT("Expected Success (bound): ~p",
+ [Port]),
+ {ok, State#{sa3 => LSA#{port => Port}}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p",
+ [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "[socket 3] verify UDP socket (after set)",
+ cmd => fun(#{sock3 := Sock} = _State) ->
+ case Get(Sock) of
+ {ok, true} ->
+ ?SEV_IPRINT("Expected Success: "
+ "broadcast allowed"),
+ ok;
+ {ok, false} ->
+ ?SEV_IPRINT("Unexpected Success result: "
+ "broadcast not allowed"),
+ {error, not_allowed};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p",
+ [Reason]),
+ ERROR
+ end
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "[socket 3] try send to limited broadcast address",
+ cmd => fun(#{sa1 := skip} = _State) ->
+ ?SEV_IPRINT("SKIP limited broadcast test"),
+ ok;
+ (#{sock3 := Sock,
+ sa1 := Dest} = _State) ->
+ Data = list_to_binary("hejsan"),
+ ?SEV_IPRINT("try send to bradcast address: "
+ "~n ~p", [Dest]),
+ case socket:sendto(Sock, Data, Dest) of
+ ok ->
+ ?SEV_IPRINT("Expected Success: "
+ "broadcast message sent"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p",
+ [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "[socket 1] try recv",
+ cmd => fun(#{sa1 := skip} = _State) ->
+ ?SEV_IPRINT("SKIP limited broadcast test"),
+ ok;
+ (#{sock1 := Sock} = State) ->
+ case socket:recvfrom(Sock, 0, 5000) of
+ {ok, _} ->
+ ?SEV_IPRINT("Expected Success: "
+ "received message"),
+ ok;
+ {error, timeout = Reason} ->
+ %% Some platforms seem to balk at this.
+ %% It spossible to bind to this, and
+ %% send to it, but no data is received.
+ %% At some point we should investigate...
+ %% For now, we just skip this part of
+ %% the test...
+ ?SEV_IPRINT("Unexpected Failure: ~p",
+ [Reason]),
+ {ok, State#{sa1 => skip}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p",
+ [Reason]),
+ ERROR
+ end
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "[socket 3] try send to subnet-directed broadcast address",
+ cmd => fun(#{sock3 := Sock,
+ sa2 := Dest} = _State) ->
+ Data = list_to_binary("hejsan"),
+ ?SEV_IPRINT("try send to bradcast address: "
+ "~n ~p", [Dest]),
+ case socket:sendto(Sock, Data, Dest) of
+ ok ->
+ ?SEV_IPRINT("Expected Success: "
+ "broadcast message sent"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p",
+ [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "[socket 2] try recv",
+ cmd => fun(#{sock2 := Sock, sa1 := SA1} = _State) ->
+ case socket:recvfrom(Sock, 0, 5000) of
+ {ok, _} ->
+ ?SEV_IPRINT("Expected Success: "
+ "received message"),
+ ok;
+ {error, timeout = Reason} when (SA1 =:= skip) ->
+ ?SEV_IPRINT("Unexpected Failure: ~p",
+ [Reason]),
+ {skip, "receive timeout"};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p",
+ [Reason]),
+ ERROR
+ end
+ end},
+
+ %% *** Termination ***
+ #{desc => "[socket 3] close UDP socket (sender)",
+ cmd => fun(#{sock3 := Sock} = State0) ->
+ socket:close(Sock),
+ State1 = maps:remove(sock3, State0),
+ State2 = maps:remove(sa3, State1),
+ {ok, State2}
+ end},
+ #{desc => "[socket 2] close UDP socket (listener 2)",
+ cmd => fun(#{sock2 := Sock} = State0) ->
+ socket:close(Sock),
+ State1 = maps:remove(sock2, State0),
+ State2 = maps:remove(sa2, State1),
+ {ok, State2}
+ end},
+ #{desc => "[socket 1] close UDP socket (listener 1)",
+ cmd => fun(#{sock1 := Sock} = State0) ->
+ socket:close(Sock),
+ State1 = maps:remove(sock1, State0),
+ State2 = maps:remove(sa1, State1),
+ {ok, State2}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ Domain = inet,
+
+ i("start tester evaluator"),
+ InitState = #{domain => Domain},
+ Tester = ?SEV_START("tester", TesterSeq, InitState),
+
+ i("await evaluator(s)"),
+ ok = ?SEV_AWAIT_FINISH([Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests the socket option debug.
+%% On linux, this test requires that the user running the test to have
+%% CAP_NET_ADMIN capabilities or be root (effective user ID of 0),
+%% therefor we explicitly test for the result eacces when attempting to
+%% set, and skip if we get it.
+
+api_opt_sock_debug(suite) ->
+ [];
+api_opt_sock_debug(doc) ->
+ [];
+api_opt_sock_debug(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_opt_sock_debug,
+ fun() -> has_support_sock_debug() end,
+ fun() -> api_opt_sock_debug() end).
+
+
+api_opt_sock_debug() ->
+ Opt = debug,
+ Set = fun(S, Val) when is_integer(Val) ->
+ socket:setopt(S, socket, Opt, Val)
+ end,
+ Get = fun(S) ->
+ socket:getopt(S, socket, Opt)
+ end,
+
+ TesterSeq =
+ [
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ case ?LIB:which_local_host_info(Domain) of
+ {ok, #{name := Name,
+ addr := Addr,
+ broadaddr := BAddr}} ->
+ ?SEV_IPRINT("local host info: "
+ "~n Name: ~p"
+ "~n Addr: ~p"
+ "~n Broadcast Addr: ~p",
+ [Name, Addr, BAddr]),
+ LSA = #{family => Domain,
+ addr => Addr},
+ BSA = #{family => Domain,
+ addr => BAddr},
+ {ok, State#{lsa => LSA,
+ bsa => BSA}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "create UDP socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, dgram, udp) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "Get current debug value",
+ cmd => fun(#{sock := Sock} = State) ->
+ case Get(Sock) of
+ {ok, Debug} when is_integer(Debug) ->
+ ?SEV_IPRINT("Success: ~p", [Debug]),
+ {ok, State#{debug => Debug}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected failure: ~p",
+ [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "Try enable socket debug",
+ cmd => fun(#{sock := Sock, debug := Debug} = State) ->
+ NewDebug = Debug + 1,
+ case Set(Sock, NewDebug) of
+ ok ->
+ ?SEV_IPRINT("Expected Success"),
+ {ok, State#{debug => NewDebug}};
+ {error, eacces = Reason} ->
+ ?SEV_EPRINT("NO ACCESS => SKIP"),
+ {skip, Reason};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p",
+ [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "Get current (new) debug value",
+ cmd => fun(#{sock := Sock, debug := Debug} = _State) ->
+ case Get(Sock) of
+ {ok, Debug} when is_integer(Debug) ->
+ ?SEV_IPRINT("Success: ~p", [Debug]),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected failure: ~p",
+ [Reason]),
+ ERROR
+ end
+ end},
+
+ %% *** Termination ***
+ #{desc => "close UDP socket",
+ cmd => fun(#{sock := Sock} = State0) ->
+ socket:close(Sock),
+ State1 = maps:remove(sock, State0),
+ {ok, State1}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ Domain = inet,
+
+ i("start tester evaluator"),
+ InitState = #{domain => Domain},
+ Tester = ?SEV_START("tester", TesterSeq, InitState),
+
+ i("await evaluator(s)"),
+ ok = ?SEV_AWAIT_FINISH([Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests the socket option domain.
+%% This is a read only option. Also not available on all platforms.
+
+api_opt_sock_domain(suite) ->
+ [];
+api_opt_sock_domain(doc) ->
+ [];
+api_opt_sock_domain(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_opt_sock_domain,
+ fun() -> has_support_sock_domain() end,
+ fun() -> api_opt_sock_domain() end).
+
+
+api_opt_sock_domain() ->
+ Opt = domain,
+ Get = fun(S) ->
+ socket:getopt(S, socket, Opt)
+ end,
+
+ TesterSeq =
+ [
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ case ?LIB:which_local_host_info(Domain) of
+ {ok, #{name := Name,
+ addr := Addr,
+ broadaddr := BAddr}} ->
+ ?SEV_IPRINT("local host info: "
+ "~n Name: ~p"
+ "~n Addr: ~p"
+ "~n Broadcast Addr: ~p",
+ [Name, Addr, BAddr]),
+ LSA = #{family => Domain,
+ addr => Addr},
+ BSA = #{family => Domain,
+ addr => BAddr},
+ {ok, State#{lsa => LSA,
+ bsa => BSA}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "create IPv4 UDP socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, dgram, udp) of
+ {ok, Sock} ->
+ {ok, State#{usock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "Get domain for the UDP socket",
+ cmd => fun(#{domain := Domain, usock := Sock} = _State) ->
+ case Get(Sock) of
+ {ok, Domain} ->
+ ?SEV_IPRINT("Success: ~p", [Domain]),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected failure: ~p",
+ [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "create TCP socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{tsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "Get domain for the TCP socket",
+ cmd => fun(#{domain := Domain, tsock := Sock} = _State) ->
+ case Get(Sock) of
+ {ok, Domain} ->
+ ?SEV_IPRINT("Success: ~p", [Domain]),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected failure: ~p",
+ [Reason]),
+ ERROR
+ end
+ end},
+
+ %% *** Termination ***
+ #{desc => "close UDP socket",
+ cmd => fun(#{usock := Sock} = State0) ->
+ socket:close(Sock),
+ State1 = maps:remove(usock, State0),
+ {ok, State1}
+ end},
+ #{desc => "close TCP socket",
+ cmd => fun(#{tsock := Sock} = State0) ->
+ socket:close(Sock),
+ State1 = maps:remove(tsock, State0),
+ {ok, State1}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ Domain = inet,
+
+ i("start tester evaluator"),
+ InitState = #{domain => Domain},
+ Tester = ?SEV_START("tester", TesterSeq, InitState),
+
+ i("await evaluator(s)"),
+ ok = ?SEV_AWAIT_FINISH([Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests the socket option dontroute.
+%% The man page has the following to say:
+%% "Don't send via a gateway, send only to directly connected hosts.
+%% The same effect can be achieved by setting the MSG_DONTROUTE
+%% flag on a socket send(2) operation."
+%% Since its "kind of" difficult to check if it actually takes an
+%% effect (you would need a gateway for that and a machine "on the
+%% other side"), we only test if we can set and get the value.
+%% Better then nothing.
+
+api_opt_sock_dontroute(suite) ->
+ [];
+api_opt_sock_dontroute(doc) ->
+ [];
+api_opt_sock_dontroute(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_opt_sock_dontroute,
+ fun() -> has_support_sock_dontroute() end,
+ fun() -> api_opt_sock_dontroute() end).
+
+
+api_opt_sock_dontroute() ->
+ Opt = dontroute,
+ Set = fun(S, Val) when is_boolean(Val) ->
+ socket:setopt(S, socket, Opt, Val)
+ end,
+ Get = fun(S) ->
+ socket:getopt(S, socket, Opt)
+ end,
+
+ TesterSeq =
+ [
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ case ?LIB:which_local_host_info(Domain) of
+ {ok, #{name := Name,
+ addr := Addr,
+ broadaddr := BAddr}} ->
+ ?SEV_IPRINT("local host info: "
+ "~n Name: ~p"
+ "~n Addr: ~p"
+ "~n Broadcast Addr: ~p",
+ [Name, Addr, BAddr]),
+ LSA = #{family => Domain,
+ addr => Addr},
+ BSA = #{family => Domain,
+ addr => BAddr},
+ {ok, State#{lsa => LSA,
+ bsa => BSA}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "create UDP socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, dgram, udp) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "Get current value",
+ cmd => fun(#{sock := Sock} = State) ->
+ case Get(Sock) of
+ {ok, Val} when is_boolean(Val) ->
+ ?SEV_IPRINT("Success: ~p", [Val]),
+ {ok, State#{dontroute => Val}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected failure: ~p",
+ [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "Try change value",
+ cmd => fun(#{sock := Sock, dontroute := Current} = State) ->
+ New = not Current,
+ ?SEV_IPRINT("Change from ~p to ~p", [Current, New]),
+ case Set(Sock, New) of
+ ok ->
+ ?SEV_IPRINT("Expected Success"),
+ {ok, State#{dontroute => New}};
+ {error, eopnotsupp = Reason} ->
+ ?SEV_EPRINT("Expected Failure: ~p",
+ [Reason]),
+ {skip, Reason};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p",
+ [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "Verify changed value",
+ cmd => fun(#{sock := Sock, dontroute := Val} = _State) ->
+ case Get(Sock) of
+ {ok, Val} ->
+ ?SEV_IPRINT("Expected Success"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected failure: ~p",
+ [Reason]),
+ ERROR
+ end
+ end},
+
+ %% *** Termination ***
+ #{desc => "close UDP socket",
+ cmd => fun(#{sock := Sock} = State0) ->
+ socket:close(Sock),
+ State1 = maps:remove(sock, State0),
+ {ok, State1}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ Domain = inet,
+
+ i("start tester evaluator"),
+ InitState = #{domain => Domain},
+ Tester = ?SEV_START("tester", TesterSeq, InitState),
+
+ i("await evaluator(s)"),
+ ok = ?SEV_AWAIT_FINISH([Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests the socket option error. PLACEHOLDER!
+
+api_opt_sock_error(suite) ->
+ [];
+api_opt_sock_error(doc) ->
+ [];
+api_opt_sock_error(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_opt_sock_error,
+ fun() -> not_yet_implemented() end,
+ fun() -> ok end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests the socket option keepalive.
+%% This is bit tricky to test, partly because we have no control over
+%% the underlying TCP timeouts. So, for now, we just test that we can
+%% change the value.
+
+api_opt_sock_keepalive(suite) ->
+ [];
+api_opt_sock_keepalive(doc) ->
+ [];
+api_opt_sock_keepalive(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_opt_sock_keepalive,
+ fun() -> has_support_sock_keepalive() end,
+ fun() -> api_opt_sock_keepalive() end).
+
+
+api_opt_sock_keepalive() ->
+ Opt = keepalive,
+ Set = fun(S, Val) when is_boolean(Val) ->
+ socket:setopt(S, socket, Opt, Val)
+ end,
+ Get = fun(S) ->
+ socket:getopt(S, socket, Opt)
+ end,
+
+ TesterSeq =
+ [
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ case ?LIB:which_local_host_info(Domain) of
+ {ok, #{name := Name,
+ addr := Addr,
+ broadaddr := BAddr}} ->
+ ?SEV_IPRINT("local host info: "
+ "~n Name: ~p"
+ "~n Addr: ~p"
+ "~n Broadcast Addr: ~p",
+ [Name, Addr, BAddr]),
+ LSA = #{family => Domain,
+ addr => Addr},
+ BSA = #{family => Domain,
+ addr => BAddr},
+ {ok, State#{lsa => LSA,
+ bsa => BSA}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "create TCP socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "Get current value",
+ cmd => fun(#{sock := Sock} = State) ->
+ case Get(Sock) of
+ {ok, Val} when is_boolean(Val) ->
+ ?SEV_IPRINT("Success: ~p", [Val]),
+ {ok, State#{keepalive => Val}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected failure: ~p",
+ [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "Try change the value",
+ cmd => fun(#{sock := Sock, keepalive := Current} = State) ->
+ New = not Current,
+ ?SEV_IPRINT("Try change value from ~p to ~p",
+ [Current, New]),
+ case Set(Sock, New) of
+ ok ->
+ ?SEV_IPRINT("Expected Success"),
+ {ok, State#{keepalive => New}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected Failure: ~p",
+ [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "Verify (new) current value",
+ cmd => fun(#{sock := Sock, keepalive := Val} = _State) ->
+ case Get(Sock) of
+ {ok, Val} ->
+ ?SEV_IPRINT("Expected Success (~p)", [Val]),
+ ok;
+ {ok, OtherVal} ->
+ ?SEV_IPRINT("Unexpected Success: ~p",
+ [OtherVal]),
+ {error, {unexpected_success_value,
+ Val, OtherVal}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected failure: ~p",
+ [Reason]),
+ ERROR
+ end
+ end},
+
+ %% *** Termination ***
+ #{desc => "close UDP socket",
+ cmd => fun(#{sock := Sock} = State0) ->
+ socket:close(Sock),
+ State1 = maps:remove(sock, State0),
+ {ok, State1}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ Domain = inet,
+
+ i("start tester evaluator"),
+ InitState = #{domain => Domain},
+ Tester = ?SEV_START("tester", TesterSeq, InitState),
+
+ i("await evaluator(s)"),
+ ok = ?SEV_AWAIT_FINISH([Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests the socket option linger. PLACEHOLDER!
+
+api_opt_sock_linger(suite) ->
+ [];
+api_opt_sock_linger(doc) ->
+ [];
+api_opt_sock_linger(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_opt_sock_linger,
+ fun() -> not_yet_implemented() end,
+ fun() -> ok end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests the socket option mark. PLACEHOLDER!
+
+api_opt_sock_mark(suite) ->
+ [];
+api_opt_sock_mark(doc) ->
+ [];
+api_opt_sock_mark(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_opt_sock_mark,
+ fun() -> not_yet_implemented() end,
+ fun() -> ok end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case tries to test that the oobinline socket 'socket' option
+%% works.
+%% So, this is done on the receiving side:
+%%
+%% socket:setopt(Sock, socket, oobinline, boolean()).
+%%
+%% This works on linux of some version (atleast linux kernel 4.15.0),
+%% but not on FreeBSD (12) for some reason. Until we have figured out
+%% exctly why, we skip a bunch of OSs...
+%%
+%% Do we need to make sure the two entities does not run in the same
+%% process? This test case does not currently do that (which works in'
+%% linux but maybe not in, say, FreeBSD).
+%%
+
+api_opt_sock_oobinline(suite) ->
+ [];
+api_opt_sock_oobinline(doc) ->
+ [];
+api_opt_sock_oobinline(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_sock_ooinline,
+ fun() ->
+ has_support_sock_oobinline(),
+ has_support_send_flag_oob(),
+ has_support_recv_flag_oob(),
+ is_valid_oobinline_platform()
+ end,
+ fun() ->
+ Set = fun(Sock, Value) ->
+ socket:setopt(Sock, socket, oobinline, Value)
+ end,
+ Get = fun(Sock) ->
+ socket:getopt(Sock, socket, oobinline)
+ end,
+ Send = fun(Sock, Data, true) ->
+ socket:send(Sock, Data, [oob]);
+ (Sock, Data, false) ->
+ socket:send(Sock, Data)
+ end,
+ Recv = fun(Sock, true) ->
+ socket:recv(Sock, 0, [oob]);
+ (Sock, false) ->
+ socket:recv(Sock)
+ end,
+ InitState = #{domain => inet,
+ proto => tcp,
+ send => Send,
+ recv => Recv,
+ set => Set,
+ get => Get},
+ ok = do_api_opt_sock_oobinline(InitState)
+ end).
+
+%% Hopefully this is a temporary solution...
+is_valid_oobinline_platform() ->
+ case os:type() of
+ {unix, linux} ->
+ ok;
+
+ Type ->
+ %% Actually, all we know is that the
+ %% test case only work for linux, but
+ %% it *should* for FreeBSD and Solaris
+ %% also...
+ not_supported(Type)
+ end.
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+do_api_opt_sock_oobinline(InitState) ->
+ process_flag(trap_exit, true),
+ ServerSeq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa => LSA}}
+ end},
+ #{desc => "create listen socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ case socket:open(Domain, stream, Proto) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{domain := local,
+ lsock := LSock,
+ lsa := LSA} = _State) ->
+ case socket:bind(LSock, LSA) of
+ {ok, _Port} ->
+ ok; % We do not care about the port for local
+ {error, _} = ERROR ->
+ ERROR
+ end;
+ (#{lsock := LSock, lsa := LSA} = State) ->
+ case sock_bind(LSock, LSA) of
+ {ok, Port} ->
+ ?SEV_IPRINT("bound to port: ~w", [Port]),
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{domain := local,
+ tester := Tester, lsa := #{path := Path}}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Path),
+ ok;
+ (#{tester := Tester, lport := Port}) ->
+ %% This is actually not used for unix domain socket
+ ?SEV_ANNOUNCE_READY(Tester, init, Port),
+ ok
+ end},
+
+
+ %% The actual test
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "await connection",
+ cmd => fun(#{lsock := LSock} = State) ->
+ case socket:accept(LSock) of
+ {ok, Sock} ->
+ ?SEV_IPRINT("accepted: ~n ~p", [Sock]),
+ {ok, State#{csock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept),
+ ok
+ end},
+
+ %% *** no oobinline ***
+
+ #{desc => "await continue (verify no oobinline)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, verify_oobinline)
+ end},
+ #{desc => "verify no oobinline",
+ cmd => fun(#{csock := Sock, get := Get} = _State) ->
+ case Get(Sock) of
+ {ok, false = _Value} ->
+ ?SEV_IPRINT("oobinline: ~p", [_Value]),
+ ok;
+ {ok, Unexpected} ->
+ ?SEV_EPRINT("Unexpected oobinline: ~p",
+ [Unexpected]),
+ {error, {unexpected_oobinline, Unexpected}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed getting oobinline:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (no oobinline)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, no_oobinline),
+ ok
+ end},
+
+ #{desc => "await continue (recv no oobinline)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv)
+ end},
+ #{desc => "await plain data",
+ cmd => fun(#{csock := Sock, recv := Recv}) ->
+ case Recv(Sock, false) of
+ {ok, <<"a">>} ->
+ ?SEV_IPRINT("received plain data"),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (plain data)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_plain),
+ ok
+ end},
+ #{desc => "await oob data",
+ cmd => fun(#{csock := Sock, recv := Recv}) ->
+ case Recv(Sock, true) of
+ {ok, <<"b">>} ->
+ ?SEV_IPRINT("received oob data"),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (oob data)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_oob),
+ ok
+ end},
+
+ %% *** oobinline ***
+
+ #{desc => "await continue (enable oobinline)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, enable_oobinline)
+ end},
+ #{desc => "enable oobinline",
+ cmd => fun(#{csock := Sock, set := Set} = _State) ->
+ case Set(Sock, true) of
+ ok ->
+ ?SEV_IPRINT("oobinline enabled"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed enable oobinline:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (oobinline)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, oobinline),
+ ok
+ end},
+
+ #{desc => "await continue (recv)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv)
+ end},
+ #{desc => "await (recv) data",
+ cmd => fun(#{csock := Sock, recv := Recv}) ->
+ case Recv(Sock, false) of
+ {ok, <<"ba">>} ->
+ ?SEV_IPRINT("received expected message: "
+ "both plain and oob data"),
+ ok;
+ {ok, BadMsg} ->
+ ?SEV_EPRINT("received unexpected message: ~p",
+ [BadMsg]),
+ {error, {unexpected_msg, BadMsg}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close connection socket",
+ cmd => fun(#{csock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(csock, State)}
+ end},
+ #{desc => "close listen socket",
+ cmd => fun(#{domain := local,
+ lsock := Sock,
+ local_sa := #{path := Path}} = State) ->
+ ok = socket:close(Sock),
+ State1 =
+ unlink_path(Path,
+ fun() ->
+ maps:remove(local_sa, State)
+ end,
+ fun() -> State end),
+ {ok, maps:remove(lsock, State1)};
+ (#{lsock := LSock} = State) ->
+ case socket:close(LSock) of
+ ok ->
+ {ok, maps:remove(lsock, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ ClientSeq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(#{domain := local} = State) ->
+ {Tester, Path} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, server_path => Path}};
+ (State) ->
+ {Tester, Port} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, server_port => Port}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** The init part ***
+ #{desc => "which server (local) address",
+ cmd => fun(#{domain := local = Domain,
+ server_path := Path} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ SSA = #{family => Domain, path => Path},
+ {ok, State#{local_sa => LSA, server_sa => SSA}};
+ (#{domain := Domain, server_port := Port} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ SSA = LSA#{port => Port},
+ {ok, State#{local_sa => LSA, server_sa => SSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ case socket:open(Domain, stream, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{sock := Sock, local_sa := LSA} = _State) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% *** The actual test ***
+ #{desc => "await continue (connect)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, connect)
+ end},
+ #{desc => "connect to server",
+ cmd => fun(#{sock := Sock, server_sa := SSA}) ->
+ socket:connect(Sock, SSA)
+ end},
+ #{desc => "announce ready (connect)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, connect),
+ ok
+ end},
+
+ %% *** First batch of data (no oobinline) ***
+
+ #{desc => "await continue (send data)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send)
+ end},
+ #{desc => "send plain data",
+ cmd => fun(#{sock := Sock, send := Send}) ->
+ Send(Sock, <<"a">>, false)
+ end},
+ %% #{desc => "enable (socket & global) debug",
+ %% cmd => fun(#{sock := Sock}) ->
+ %% ok = socket:setopt(Sock, otp, debug, true),
+ %% ok = socket:debug(true)
+ %% end},
+ #{desc => "send oob data",
+ cmd => fun(#{sock := Sock, send := Send}) ->
+ Send(Sock, <<"b">>, true)
+ end},
+ %% #{desc => "disable (socket) debug",
+ %% cmd => fun(#{sock := Sock}) ->
+ %% ok = socket:debug(true),
+ %% ok = socket:setopt(Sock, otp, debug, false)
+ %% end},
+ #{desc => "announce ready (send)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send),
+ ok
+ end},
+
+ %% *** Second batch of data (oobinline) ***
+
+ #{desc => "await continue (send data)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send)
+ end},
+ #{desc => "send plain data",
+ cmd => fun(#{sock := Sock, send := Send}) ->
+ Send(Sock, <<"a">>, false)
+ end},
+ #{desc => "send oob data",
+ cmd => fun(#{sock := Sock, send := Send}) ->
+ Send(Sock, <<"b">>, true)
+ end},
+ #{desc => "announce ready (send)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{domain := local,
+ sock := Sock,
+ local_sa := #{path := Path}} = State) ->
+ ok = socket:close(Sock),
+ State1 =
+ unlink_path(Path,
+ fun() ->
+ maps:remove(local_sa, State)
+ end,
+ fun() -> State end),
+ {ok, maps:remove(sock, State1)};
+ (#{sock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = State) ->
+ {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{server_port => Port}}
+ end},
+
+ %% Start the client
+ #{desc => "order client start",
+ cmd => fun(#{client := Pid, server_port := Port} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Port),
+ ok
+ end},
+ #{desc => "await client ready (init)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client, init)
+ end},
+
+ %% *** The actual test ***
+ #{desc => "order server to continue (with accept)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, accept),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order client to continue (with connect)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, connect),
+ ok
+ end},
+ #{desc => "await client ready (connect)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, connect)
+ end},
+ #{desc => "await server ready (accept)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, accept)
+ end},
+
+ %% *** First batch of data (no oobinline) ***
+
+ #{desc => "order server to continue (with verify no oobinline)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, verify_oobinline),
+ ok
+ end},
+ #{desc => "await server ready (no oobinline)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, no_oobinline)
+ end},
+
+ #{desc => "order client to continue (with send)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send),
+ ok
+ end},
+ #{desc => "await client ready (with send)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, send)
+ end},
+ #{desc => "order server to continue (with recv plain)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, recv),
+ ok
+ end},
+ #{desc => "await server ready (recv plain)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv_plain)
+ end},
+ #{desc => "await server ready (recv oob)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv_oob)
+ end},
+
+ %% Second message (w timestamp)
+
+ #{desc => "order server to continue (with enable oobinline)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, enable_oobinline),
+ ok
+ end},
+ #{desc => "await server ready (oobinline)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, oobinline)
+ end},
+
+ #{desc => "order client to continue (with send)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send),
+ ok
+ end},
+ #{desc => "await client ready (with send)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, send)
+ end},
+ #{desc => "order server to continue (with recv)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, recv),
+ ok
+ end},
+ #{desc => "await server ready (recv plain)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv)
+ end},
+
+
+ %% *** Termination ***
+ #{desc => "order client to terminate",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await client termination",
+ cmd => fun(#{client := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(client, State),
+ {ok, State1}
+ end},
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Server),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Server} = State) ->
+ ?SEV_AWAIT_TERMINATION(Server),
+ State1 = maps:remove(server, State),
+ {ok, State1}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ i("start server evaluator"),
+ Server = ?SEV_START("server", ServerSeq, InitState),
+
+ i("start client evaluator"),
+ Client = ?SEV_START("client", ClientSeq, InitState),
+
+ i("start tester evaluator"),
+ TesterInitState = #{server => Server#ev.pid,
+ client => Client#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests that the credentials control message header is received when
+%% setting the socket 'socket' option true when using sendmsg/recvmsg
+%% on an IPv4 TCP (stream) socket.
+%% So, this is done on the receiving side:
+%%
+%% socket:setopt(Sock, socket, passcred, boolean()).
+%%
+%% We *may* need to run the different entities (server and client) in
+%% separate VM (os processes) for this to actually work.
+%% As it is now, the client does *not* get any credentials!
+%% Until this has been done, this case is skipped!.
+
+api_opt_sock_passcred_tcp4(suite) ->
+ [];
+api_opt_sock_passcred_tcp4(doc) ->
+ [];
+api_opt_sock_passcred_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_sock_passcred_tcp4,
+ fun() -> has_support_sock_passcred(),
+ not_yet_implemented()
+ end,
+ fun() ->
+ Set = fun(Sock, Value) ->
+ socket:setopt(Sock, socket, passcred, Value)
+ end,
+ Get = fun(Sock) ->
+ socket:getopt(Sock, socket, passcred)
+ end,
+ Send = fun(Sock, Data) ->
+ MsgHdr = #{iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock) of
+ {ok, #{ctrl := CMsgHdrs,
+ iov := [Data]}} ->
+ {ok, {CMsgHdrs, Data}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet,
+ proto => tcp,
+ send => Send,
+ recv => Recv,
+ set => Set,
+ get => Get},
+ ok = api_opt_sock_passcred_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_opt_sock_passcred_tcp(InitState) ->
+ process_flag(trap_exit, true),
+ ServerSeq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa => LSA}}
+ end},
+ #{desc => "create listen socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ case socket:open(Domain, stream, Proto) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{domain := local,
+ lsock := LSock,
+ lsa := LSA} = _State) ->
+ case socket:bind(LSock, LSA) of
+ {ok, _Port} ->
+ ok; % We do not care about the port for local
+ {error, _} = ERROR ->
+ ERROR
+ end;
+ (#{lsock := LSock, lsa := LSA} = State) ->
+ case sock_bind(LSock, LSA) of
+ {ok, Port} ->
+ ?SEV_IPRINT("bound to port: ~w", [Port]),
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{domain := local,
+ tester := Tester, lsa := #{path := Path}}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Path),
+ ok;
+ (#{tester := Tester, lport := Port}) ->
+ %% This is actually not used for unix domain socket
+ ?SEV_ANNOUNCE_READY(Tester, init, Port),
+ ok
+ end},
+
+
+ %% The actual test
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "await connection",
+ cmd => fun(#{lsock := LSock} = State) ->
+ case socket:accept(LSock) of
+ {ok, Sock} ->
+ ?SEV_IPRINT("accepted: ~n ~p", [Sock]),
+ {ok, State#{csock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept),
+ ok
+ end},
+
+ %% *** First message ***
+
+ #{desc => "await (recv) request 1",
+ cmd => fun(#{csock := Sock, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {[], ?BASIC_REQ}} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv request)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_req),
+ ok
+ end},
+ #{desc => "await continue (with send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply)
+ end},
+ #{desc => "send reply",
+ cmd => fun(#{csock := Sock, send := Send}) ->
+ Send(Sock, ?BASIC_REP)
+ end},
+ #{desc => "announce ready (send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_reply),
+ ok
+ end},
+
+ %% *** Second message ***
+
+ #{desc => "await (recv) request 2",
+ cmd => fun(#{csock := Sock, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {[], ?BASIC_REQ}} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv request)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_req),
+ ok
+ end},
+ #{desc => "await continue (with send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply)
+ end},
+ #{desc => "send reply",
+ cmd => fun(#{csock := Sock, send := Send}) ->
+ Send(Sock, ?BASIC_REP)
+ end},
+ #{desc => "announce ready (send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_reply),
+ ok
+ end},
+
+ %% *** Third message ***
+
+ #{desc => "await (recv) request 3",
+ cmd => fun(#{csock := Sock, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {[], ?BASIC_REQ}} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv request)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_req),
+ ok
+ end},
+ #{desc => "await continue (with send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply)
+ end},
+ #{desc => "send reply",
+ cmd => fun(#{csock := Sock, send := Send}) ->
+ Send(Sock, ?BASIC_REP)
+ end},
+ #{desc => "announce ready (send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_reply),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close connection socket",
+ cmd => fun(#{csock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(csock, State)}
+ end},
+ #{desc => "close listen socket",
+ cmd => fun(#{domain := local,
+ lsock := Sock,
+ local_sa := #{path := Path}} = State) ->
+ ok = socket:close(Sock),
+ State1 =
+ unlink_path(Path,
+ fun() ->
+ maps:remove(local_sa, State)
+ end,
+ fun() -> State end),
+ {ok, maps:remove(lsock, State1)};
+ (#{lsock := LSock} = State) ->
+ case socket:close(LSock) of
+ ok ->
+ {ok, maps:remove(lsock, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ ClientSeq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(#{domain := local} = State) ->
+ {Tester, Path} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, server_path => Path}};
+ (State) ->
+ {Tester, Port} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, server_port => Port}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** The init part ***
+ #{desc => "which server (local) address",
+ cmd => fun(#{domain := local = Domain,
+ server_path := Path} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ SSA = #{family => Domain, path => Path},
+ {ok, State#{local_sa => LSA, server_sa => SSA}};
+ (#{domain := Domain, server_port := Port} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ SSA = LSA#{port => Port},
+ {ok, State#{local_sa => LSA, server_sa => SSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ case socket:open(Domain, stream, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{sock := Sock, local_sa := LSA} = _State) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% *** The actual test ***
+ #{desc => "await continue (connect)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, connect)
+ end},
+ #{desc => "connect to server",
+ cmd => fun(#{sock := Sock, server_sa := SSA}) ->
+ socket:connect(Sock, SSA)
+ end},
+ #{desc => "announce ready (connect)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, connect),
+ ok
+ end},
+
+ %% *** First message (default=wo passcred) ***
+
+ #{desc => "await continue (verify timestamp off)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, verify_passcred)
+ end},
+ #{desc => "verify passcred off",
+ cmd => fun(#{sock := Sock, get := Get} = _State) ->
+ case Get(Sock) of
+ {ok, false = _Value} ->
+ ?SEV_IPRINT("passcred: ~p", [_Value]),
+ ok;
+ {ok, Unexpected} ->
+ ?SEV_EPRINT("Unexpected passcred: ~p",
+ [Unexpected]),
+ {error, {unexpected_passcred, Unexpected}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed getting passcred:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (passcred off)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, passcred_off),
+ ok
+ end},
+
+ #{desc => "await continue (send request)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_req)
+ end},
+ #{desc => "send request 1 (to server)",
+ cmd => fun(#{sock := Sock, send := Send}) ->
+ Send(Sock, ?BASIC_REQ)
+ end},
+ #{desc => "announce ready (send request)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_req),
+ ok
+ end},
+ #{desc => "await recv reply 1 (from server, wo passcred)",
+ cmd => fun(#{sock := Sock, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {[], ?BASIC_REP}} ->
+ ok;
+ {ok, {[], UnexpData}} ->
+ {error, {unexpected_reply_data, UnexpData}};
+ {ok, {BadCMsgHdrs, ?BASIC_REP}} ->
+ {error, {unexpected_reply_cmsghdrs,
+ BadCMsgHdrs}};
+ {ok, BadReply} ->
+ {error, {unexpected_reply, BadReply}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready 1 (recv reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_reply),
+ ok
+ end},
+
+ %% *** Second message (w passcred) ***
+
+ #{desc => "await continue (enable passcred)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, enable_passcred)
+ end},
+ #{desc => "enable passcred",
+ cmd => fun(#{sock := Sock, set := Set} = _State) ->
+ case Set(Sock, true) of
+ ok ->
+ ?SEV_IPRINT("passcred enabled"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed enable passcred:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (passcred on)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, passcred_on),
+ ok
+ end},
+
+ #{desc => "await continue (send request 2)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_req)
+ end},
+ #{desc => "send request 2 (to server)",
+ cmd => fun(#{sock := Sock, send := Send}) ->
+ Send(Sock, ?BASIC_REQ)
+ end},
+ #{desc => "announce ready (send request 2)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_req),
+ ok
+ end},
+ #{desc => "await recv reply 2 (from server, w passcred)",
+ cmd => fun(#{sock := Sock, recv := Recv}) ->
+ %% socket:setopt(Sock, otp, debug, true),
+ case Recv(Sock) of
+ {ok, {[#{level := socket,
+ type := passcred,
+ data := Cred}], ?BASIC_REP}} ->
+ %% socket:setopt(Sock, otp, debug, false),
+ ?SEV_IPRINT("received reply *with* "
+ "expected passcred: "
+ "~n ~p", [Cred]),
+ ok;
+ {ok, {BadCMsgHdrs, ?BASIC_REP}} ->
+ %% socket:setopt(Sock, otp, debug, false),
+ {error, {unexpected_reply_cmsghdrs,
+ BadCMsgHdrs}};
+ {ok, {[#{level := socket,
+ type := passcred,
+ data := _Cred}], BadData}} ->
+ %% socket:setopt(Sock, otp, debug, false),
+ {error, {unexpected_reply_data,
+ BadData}};
+ {ok, BadReply} ->
+ %% socket:setopt(Sock, otp, debug, false),
+ {error, {unexpected_reply, BadReply}};
+ {error, _} = ERROR ->
+ %% socket:setopt(Sock, otp, debug, false),
+ ERROR
+ end
+ end},
+ #{desc => "announce ready 2 (recv reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_reply),
+ ok
+ end},
+
+ %% *** Third message (wo passcred) ***
+
+ #{desc => "await continue (disable passcred)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, disable_passcred)
+ end},
+ #{desc => "disable passcred",
+ cmd => fun(#{sock := Sock, set := Set} = _State) ->
+ case Set(Sock, false) of
+ ok ->
+ ?SEV_IPRINT("passcred disabled"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed disable timestamp:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (passcred off)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, passcred_off),
+ ok
+ end},
+
+ #{desc => "await continue (send request 3)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_req)
+ end},
+ #{desc => "send request 3 (to server)",
+ cmd => fun(#{sock := Sock, send := Send}) ->
+ Send(Sock, ?BASIC_REQ)
+ end},
+ #{desc => "announce ready (send request 3)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_req),
+ ok
+ end},
+ #{desc => "await recv reply 3 (from server, wo passcred)",
+ cmd => fun(#{sock := Sock, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {[], ?BASIC_REP}} ->
+ ?SEV_IPRINT("received reply *without* "
+ "passcred"),
+ ok;
+ {ok, {BadCMsgHdrs, ?BASIC_REP}} ->
+ {error, {unexpected_reply_cmsghdrs,
+ BadCMsgHdrs}};
+ {ok, {[], BadData}} ->
+ {error, {unexpected_reply_data,
+ BadData}};
+ {ok, BadReply} ->
+ {error, {unexpected_reply, BadReply}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready 3 (recv reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_reply),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{domain := local,
+ sock := Sock,
+ local_sa := #{path := Path}} = State) ->
+ ok = socket:close(Sock),
+ State1 =
+ unlink_path(Path,
+ fun() ->
+ maps:remove(local_sa, State)
+ end,
+ fun() -> State end),
+ {ok, maps:remove(sock, State1)};
+ (#{sock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = State) ->
+ {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{server_port => Port}}
+ end},
+
+ %% Start the client
+ #{desc => "order client start",
+ cmd => fun(#{client := Pid, server_port := Port} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Port),
+ ok
+ end},
+ #{desc => "await client ready (init)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client, init)
+ end},
+
+ %% *** The actual test ***
+ #{desc => "order server to continue (with accept)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, accept),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order client to continue (with connect)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, connect),
+ ok
+ end},
+ #{desc => "await client ready (connect)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, connect)
+ end},
+ #{desc => "await server ready (accept)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, accept)
+ end},
+
+ %% *** First message (default=wo passcred) ***
+
+ #{desc => "order client to continue (with verify timestamp off)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, verify_passcred),
+ ok
+ end},
+ #{desc => "await client ready (passcred off)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, passcred_off)
+ end},
+
+ #{desc => "order client to continue (with send request 1)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send_req),
+ ok
+ end},
+ #{desc => "await client ready (with send request 1)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, send_req)
+ end},
+ #{desc => "await server ready (request recv 1)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv_req)
+ end},
+ #{desc => "order server to continue (with send reply)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, send_reply),
+ ok
+ end},
+ #{desc => "await server ready (with reply sent)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, send_reply)
+ end},
+ #{desc => "await client ready (reply recv)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, recv_reply)
+ end},
+
+ %% Second message (w passcred)
+
+ #{desc => "order client to continue (with enable passcred)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, enable_passcred),
+ ok
+ end},
+ #{desc => "await client ready (passcred on)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, passcred_on)
+ end},
+
+ #{desc => "order client to continue (with send request 2)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send_req),
+ ok
+ end},
+ #{desc => "await client ready (with send request 2)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, send_req)
+ end},
+ #{desc => "await server ready (request recv 2)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv_req)
+ end},
+ #{desc => "order server to continue (with send reply 2)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, send_reply),
+ ok
+ end},
+ #{desc => "await server ready (with reply sent 2)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, send_reply)
+ end},
+ #{desc => "await client ready (reply recv 2)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, recv_reply)
+ end},
+
+ %% Third message (wo passcred)
+
+ #{desc => "order client to continue (with disable passcred)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, disable_passcred),
+ ok
+ end},
+ #{desc => "await client ready (passcred off)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, passcred_off)
+ end},
+
+ #{desc => "order client to continue (with send request 3)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send_req),
+ ok
+ end},
+ #{desc => "await client ready (with send request 3)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, send_req)
+ end},
+ #{desc => "await server ready (request recv 3)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv_req)
+ end},
+ #{desc => "order server to continue (with send reply 3)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, send_reply),
+ ok
+ end},
+ #{desc => "await server ready (with reply sent 3)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, send_reply)
+ end},
+ #{desc => "await client ready (reply recv 3)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, recv_reply)
+ end},
+
+
+ %% *** Termination ***
+ #{desc => "order client to terminate",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await client termination",
+ cmd => fun(#{client := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(client, State),
+ {ok, State1}
+ end},
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Server),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Server} = State) ->
+ ?SEV_AWAIT_TERMINATION(Server),
+ State1 = maps:remove(server, State),
+ {ok, State1}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ i("start server evaluator"),
+ Server = ?SEV_START("server", ServerSeq, InitState),
+
+ i("start client evaluator"),
+ Client = ?SEV_START("client", ClientSeq, InitState),
+
+ i("start tester evaluator"),
+ TesterInitState = #{server => Server#ev.pid,
+ client => Client#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests the the peek-off socket option for a unix domain socket
+%% (stream TCP in this case).
+%%
+%% THIS IS A PLACEHOLDER!!
+%%
+%%
+
+api_opt_sock_peek_off_tcpL(suite) ->
+ [];
+api_opt_sock_peek_off_tcpL(doc) ->
+ [];
+api_opt_sock_peek_off_tcpL(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_sock_peek_off_tcpL,
+ fun() ->
+ has_support_unix_domain_socket(),
+ has_support_sock_peek_off(),
+ has_support_recv_flag_peek()
+ end,
+ fun() ->
+ Set = fun(Sock, Val) when is_integer(Val) ->
+ socket:setopt(Sock, socket, peek_off, Val)
+ end,
+ Get = fun(Sock) ->
+ socket:getopt(Sock, socket, peek_off)
+ end,
+ Send = fun(Sock, Data) ->
+ socket:send(Sock, Data)
+ end,
+ Recv = fun(Sock, L, false) ->
+ socket:recv(Sock, L);
+ (Sock, L, true) ->
+ socket:recv(Sock, L, [peek])
+ end,
+ InitState = #{domain => local,
+ proto => default, % Type = stream => tcp
+ set => Set,
+ get => Get,
+ send => Send,
+ recv => Recv},
+ ok = api_opt_sock_peek_off(InitState)
+ end).
+
+api_opt_sock_peek_off(InitState) ->
+ process_flag(trap_exit, true),
+ ServerSeq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa => LSA}}
+ end},
+ #{desc => "create listen socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ case socket:open(Domain, stream, Proto) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{lsock := LSock,
+ lsa := LSA} = _State) ->
+ case sock_bind(LSock, LSA) of
+ {ok, _Port} ->
+ ok; % We do not care about the port for local
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, lsa := #{path := Path}}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Path),
+ ok
+ end},
+
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "await connection",
+ cmd => fun(#{lsock := LSock} = State) ->
+ case socket:accept(LSock) of
+ {ok, Sock} ->
+ ?SEV_IPRINT("accepted: ~n ~p", [Sock]),
+ {ok, State#{csock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept),
+ ok
+ end},
+
+
+ %% The actual test
+
+ %% 1) peek (0 = everything: 1,2,3,4,5,6,7,8)
+ #{desc => "1a: await continue (peek)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, peek)
+ end},
+ #{desc => "1a: peek read",
+ cmd => fun(#{csock := Sock,
+ recv := Recv} = _State) ->
+ case Recv(Sock, 0, true) of
+ {ok, <<1,2,3,4,5,6,7,8>>} ->
+ ?SEV_IPRINT("peek'ed expected data"),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "1a: announce ready (peek)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, peek),
+ ok
+ end},
+
+ #{desc => "1b: await continue (verify peek-off)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, verify_peek_off)
+ end},
+ #{desc => "1b: verify peek-off",
+ cmd => fun(#{csock := Sock,
+ get := Get} = _State) ->
+ case Get(Sock) of
+ {ok, DefaultPeekOff} ->
+ ?SEV_IPRINT("verify peek-off: ~w",
+ [DefaultPeekOff]),
+ ok;
+ {error, {not_supported, {socket, peek_off}}} ->
+ {skip, "Not supported"};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "1b: announce ready (verify peek-off)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, verify_peek_off),
+ ok
+ end},
+
+
+ %% 2) set peek-off to 4
+ #{desc => "2a: await continue (set peek-off: 4)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, set_peek_off)
+ end},
+ #{desc => "2a: set peek-off: 4",
+ cmd => fun(#{csock := Sock,
+ set := Set} = _State) ->
+ case Set(Sock, 4) of
+ ok ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "2a: announce ready (set peek-off: 4)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, set_peek_off),
+ ok
+ end},
+
+ #{desc => "2b: await continue (verify peek-off)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, verify_peek_off)
+ end},
+ #{desc => "2b: verify peek-off",
+ cmd => fun(#{csock := Sock,
+ get := Get} = _State) ->
+ case Get(Sock) of
+ {ok, 4 = PeekOff} ->
+ ?SEV_IPRINT("verify peek-off: ~w", [PeekOff]),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "2b: announce ready (verify peek-off)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, verify_peek_off),
+ ok
+ end},
+
+
+ %% 3) peek (0 = everything: 5,6,7,8)
+ %% NOTE THAT THIS WILL MOVE THE PEEK-OFF "POINTER" TO THE END OF
+ %% THE *CURRENT* DATA POSITION IN THE BUFFER (READY FOR NEXT BATCH
+ %% OF DATA).
+ #{desc => "3a: await continue (peek)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, peek)
+ end},
+ #{desc => "3a: peek read",
+ cmd => fun(#{csock := Sock,
+ recv := Recv} = _State) ->
+ case Recv(Sock, 0, true) of
+ {ok, <<5,6,7,8>>} ->
+ ?SEV_IPRINT("peek'ed expected data"),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "3a: announce ready (peek)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, peek),
+ ok
+ end},
+
+ #{desc => "3b: await continue (verify peek-off)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, verify_peek_off)
+ end},
+ #{desc => "3b: verify peek-off",
+ cmd => fun(#{csock := Sock,
+ get := Get} = _State) ->
+ case Get(Sock) of
+ {ok, 8 = PeekOff} ->
+ ?SEV_IPRINT("verify peek-off: ~w", [PeekOff]),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "3b: announce ready (verify peek-off)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, verify_peek_off),
+ ok
+ end},
+
+
+ %% 4) read two byte(s): 1,2
+ #{desc => "4a: await continue (read 2 byte)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, read)
+ end},
+ #{desc => "4a: read (2 bytes)",
+ cmd => fun(#{csock := Sock,
+ recv := Recv} = _State) ->
+ case Recv(Sock, 2, false) of
+ {ok, <<1,2>>} ->
+ ?SEV_IPRINT("read expected data"),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "4a: announce ready (read)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, read),
+ ok
+ end},
+
+ #{desc => "4b: await continue (verify peek-off)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, verify_peek_off)
+ end},
+ #{desc => "4b: verify peek-off",
+ cmd => fun(#{csock := Sock,
+ get := Get} = _State) ->
+ case Get(Sock) of
+ {ok, 6 = PeekOff} ->
+ ?SEV_IPRINT("verify peek-off: ~w", [PeekOff]),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "4b: announce ready (verify peek-off)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, verify_peek_off),
+ ok
+ end},
+
+
+ %% 5) read the rest: 3,4,5,6,7,8)
+ #{desc => "5a: await continue (read the rest)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, read)
+ end},
+ #{desc => "5a: read (the rest)",
+ cmd => fun(#{csock := Sock,
+ recv := Recv} = _State) ->
+ case Recv(Sock, 0, false) of
+ {ok, <<3,4,5,6,7,8>>} ->
+ ?SEV_IPRINT("read expected data"),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "5a: announce ready (read)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, read),
+ ok
+ end},
+
+ #{desc => "5b: await continue (verify peek-off)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, verify_peek_off)
+ end},
+ #{desc => "5b: verify peek-off",
+ cmd => fun(#{csock := Sock,
+ get := Get} = _State) ->
+ case Get(Sock) of
+ {ok, PeekOff} ->
+ ?SEV_IPRINT("verify peek-off: ~w", [PeekOff]),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "5b: announce ready (verify peek-off)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, verify_peek_off),
+ ok
+ end},
+
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close connection socket",
+ cmd => fun(#{csock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(csock, State)}
+ end},
+ #{desc => "close listen socket",
+ cmd => fun(#{lsock := Sock,
+ lsa := #{path := Path}} = State) ->
+ ok = socket:close(Sock),
+ State1 =
+ unlink_path(Path,
+ fun() ->
+ maps:remove(local_sa, State)
+ end,
+ fun() -> State end),
+ {ok, maps:remove(lsock, State1)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ ClientSeq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(#{domain := local} = State) ->
+ {Tester, Path} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, server_path => Path}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** The init part ***
+ #{desc => "which server (local) address",
+ cmd => fun(#{domain := local = Domain,
+ server_path := Path} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ SSA = #{family => Domain, path => Path},
+ {ok, State#{local_sa => LSA, server_sa => SSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ case socket:open(Domain, stream, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{sock := Sock, local_sa := LSA} = _State) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ #{desc => "await continue (connect)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, connect)
+ end},
+ #{desc => "connect to server",
+ cmd => fun(#{sock := Sock, server_sa := SSA}) ->
+ socket:connect(Sock, SSA)
+ end},
+ #{desc => "announce ready (connect)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, connect),
+ ok
+ end},
+
+
+ %% *** The actual test ***
+ #{desc => "await continue (send data)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_data)
+ end},
+ #{desc => "send data (to server)",
+ cmd => fun(#{sock := Sock, send := Send}) ->
+ Send(Sock, <<1:8/integer,
+ 2:8/integer,
+ 3:8/integer,
+ 4:8/integer,
+ 5:8/integer,
+ 6:8/integer,
+ 7:8/integer,
+ 8:8/integer>>)
+ end},
+ #{desc => "announce ready (send data)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_data),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock,
+ local_sa := #{path := Path}} = State) ->
+ ok = socket:close(Sock),
+ State1 =
+ unlink_path(Path,
+ fun() ->
+ maps:remove(local_sa, State)
+ end,
+ fun() -> State end),
+ {ok, maps:remove(sock, State1)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = State) ->
+ {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{server_port => Port}}
+ end},
+
+ %% Start the client
+ #{desc => "order client start",
+ cmd => fun(#{client := Pid, server_port := Port} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Port),
+ ok
+ end},
+ #{desc => "await client ready (init)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client, init)
+ end},
+
+ %% Establish the connection
+ #{desc => "order server to continue (with accept)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, accept),
+ ok
+ end},
+ #{desc => "order client to continue (with connect)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, connect),
+ ok
+ end},
+ #{desc => "await client ready (connect)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, connect)
+ end},
+ #{desc => "await server ready (accept)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, accept)
+ end},
+
+
+ %% *** The actual test ***
+ #{desc => "order client to continue (with send data)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send_data),
+ ok
+ end},
+ #{desc => "await client ready (with send data)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, send_data)
+ end},
+
+ %% There is no way to be sure that the data has actually arrived,
+ %% and with no data on the server side, the peek will fail.
+ %% Hopfully a sleep will take care of this...
+ ?SEV_SLEEP(?SECS(1)),
+
+ %% 1) peek
+ #{desc => "1a: order server to continue (peek)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, peek),
+ ok
+ end},
+ #{desc => "1a: await server ready (peek)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, peek)
+ end},
+
+ #{desc => "1b: order server to continue (verify peek-off)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, verify_peek_off),
+ ok
+ end},
+ #{desc => "1b: await server ready (verify peek-off)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, verify_peek_off)
+ end},
+
+
+ %% 2) set peek-off
+ #{desc => "2a: order server to continue (set peek-off)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, set_peek_off),
+ ok
+ end},
+ #{desc => "2a: await server ready (set peek-off)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, set_peek_off)
+ end},
+
+ #{desc => "2b: order server to continue (verify peek-off)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, verify_peek_off),
+ ok
+ end},
+ #{desc => "2b: await server ready (verify peek-off)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, verify_peek_off)
+ end},
+
+
+
+ %% 3) peek
+ #{desc => "3a: order server to continue (peek)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, peek),
+ ok
+ end},
+ #{desc => "3a: await server ready (peek)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, peek)
+ end},
+
+ #{desc => "3b: order server to continue (verify peek-off)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, verify_peek_off),
+ ok
+ end},
+ #{desc => "3b: await server ready (verify peek-off)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, verify_peek_off)
+ end},
+
+
+
+ %% 4) read part
+ #{desc => "4a: order server to continue (read part)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, read),
+ ok
+ end},
+ #{desc => "4a: await server ready (peek)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, read)
+ end},
+
+ #{desc => "4b: order server to continue (verify peek-off)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, verify_peek_off),
+ ok
+ end},
+ #{desc => "4b: await server ready (verify peek-off)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, verify_peek_off)
+ end},
+
+
+ %% 5) read (the rest)
+ #{desc => "5a: order server to continue (read the rest)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, read),
+ ok
+ end},
+ #{desc => "5a: await server ready (peek)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, read)
+ end},
+
+ #{desc => "5b: order server to continue (verify peek-off)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, verify_peek_off),
+ ok
+ end},
+ #{desc => "5b: await server ready (verify peek-off)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, verify_peek_off)
+ end},
+
+
+ %% *** Termination ***
+ #{desc => "order client to terminate",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await client termination",
+ cmd => fun(#{client := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(client, State),
+ {ok, State1}
+ end},
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Server),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Server} = State) ->
+ ?SEV_AWAIT_TERMINATION(Server),
+ State1 = maps:remove(server, State),
+ {ok, State1}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start server evaluator"),
+ Server = ?SEV_START("server", ServerSeq, InitState),
+
+ i("start client evaluator"),
+ Client = ?SEV_START("client", ClientSeq, InitState),
+ i("await evaluator(s)"),
+
+ i("start tester evaluator"),
+ TesterInitState = #{server => Server#ev.pid,
+ client => Client#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests that we get the peer credentials for a connected unix domain
+%% TCP (stream) socket.
+%% That is, all we need to do is to create a slave node, and have
+%% process connect from that to a local (unix domain socket) socket.
+%%
+%% THIS IS A PLACEHOLDER!!
+%%
+%% We need to figure out what the ucred structure looks like,
+%% and decode it...
+%%
+
+api_opt_sock_peercred_tcpL(suite) ->
+ [];
+api_opt_sock_peercred_tcpL(doc) ->
+ [];
+api_opt_sock_peercred_tcpL(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_sock_peercred_tcpL,
+ fun() ->
+ has_support_unix_domain_socket(),
+ has_support_sock_peercred(),
+ not_yet_implemented()
+ end,
+ fun() ->
+ Get = fun(Sock) ->
+ socket:getopt(Sock, socket, peercred)
+ end,
+ InitState = #{domain => local,
+ proto => default, % Type = stream => tcp
+ get => Get},
+ ok = api_opt_sock_peercred_tcp(InitState)
+ end).
+
+
+api_opt_sock_peercred_tcp(_InitState) ->
+ %% ServerSeq =
+ %% [
+ %% %% *** Wait for start order part ***
+ %% #{desc => "await start (from tester)",
+ %% cmd => fun(State) ->
+ %% {Tester, Backlog} = ?SEV_AWAIT_START(),
+ %% {ok, State#{tester => Tester,
+ %% backlog => Backlog}}
+ %% end},
+ %% #{desc => "monitor tester",
+ %% cmd => fun(#{tester := Tester} = _State) ->
+ %% _MRef = erlang:monitor(process, Tester),
+ %% ok
+ %% end},
+
+ %% %% *** Init part ***
+ %% #{desc => "which local address",
+ %% cmd => fun(#{domain := Domain} = State) ->
+ %% LSA = which_local_socket_addr(Domain),
+ %% {ok, State#{lsa => LSA}}
+ %% end},
+ %% #{desc => "create listen socket",
+ %% cmd => fun(#{domain := Domain, proto := Proto} = State) ->
+ %% case socket:open(Domain, stream, Proto) of
+ %% {ok, Sock} ->
+ %% {ok, State#{lsock => Sock}};
+ %% {error, _} = ERROR ->
+ %% ERROR
+ %% end
+ %% end},
+ %% #{desc => "bind to local address",
+ %% cmd => fun(#{domain := local,
+ %% lsock := LSock,
+ %% lsa := LSA} = _State) ->
+ %% case socket:bind(LSock, LSA) of
+ %% {ok, _Port} ->
+ %% ok;
+ %% {error, _} = ERROR ->
+ %% ERROR
+ %% end
+ %% end},
+ %% #{desc => "make listen socket",
+ %% cmd => fun(#{lsock := LSock}) ->
+ %% socket:listen(LSock)
+ %% end},
+ %% #{desc => "announce ready (init)",
+ %% cmd => fun(#{domain := local,
+ %% tester := Tester, lsa := #{path := Path}}) ->
+ %% ?SEV_ANNOUNCE_READY(Tester, init, Path),
+ %% ok
+ %% end},
+
+
+ %% %% The actual test
+ %% #{desc => "await continue (accept)",
+ %% cmd => fun(#{tester := Tester}) ->
+ %% ?SEV_AWAIT_CONTINUE(Tester, tester, accept)
+ %% end},
+ %% #{desc => "await connection",
+ %% cmd => fun(#{lsock := LSock} = State) ->
+ %% case socket:accept(LSock) of
+ %% {ok, Sock} ->
+ %% ?SEV_IPRINT("accepted: ~n ~p", [Sock]),
+ %% {ok, State#{csock => Sock}};
+ %% {error, _} = ERROR ->
+ %% ERROR
+ %% end
+ %% end},
+ %% #{desc => "announce ready (accept)",
+ %% cmd => fun(#{tester := Tester}) ->
+ %% ?SEV_ANNOUNCE_READY(Tester, accept),
+ %% ok
+ %% end},
+
+ %% #{desc => "await continue (peercred)",
+ %% cmd => fun(#{tester := Tester}) ->
+ %% ?SEV_AWAIT_CONTINUE(Tester, tester, peercred)
+ %% end},
+ %% #{desc => "get peercred",
+ %% cmd => fun(#{csock := Sock, get := Get} = _State) ->
+ %% case Get(Sock) of
+ %% {ok, PeerCred} ->
+ %% ?SEV_IPRINT("PeerCred: ~n ~p", [PeerCred]),
+ %% ok;
+ %% {error, _} = ERROR ->
+ %% ERROR
+ %% end
+ %% end},
+ %% #{desc => "announce ready (peercred)",
+ %% cmd => fun(#{tester := Tester}) ->
+ %% ?SEV_ANNOUNCE_READY(Tester, peercred),
+ %% ok
+ %% end},
+
+
+ %% %% Termination
+ %% #{desc => "await terminate",
+ %% cmd => fun(#{tester := Tester} = State) ->
+ %% case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ %% ok ->
+ %% {ok, maps:remove(tester, State)};
+ %% {error, _} = ERROR ->
+ %% ERROR
+ %% end
+ %% end},
+ %% #{desc => "close connection socket",
+ %% cmd => fun(#{csock := Sock} = State) ->
+ %% ok = socket:close(Sock),
+ %% {ok, maps:remove(csock, State)}
+ %% end},
+ %% #{desc => "close listen socket",
+ %% cmd => fun(#{domain := local,
+ %% lsock := Sock,
+ %% lsa := #{path := Path}} = State) ->
+ %% ok = socket:close(Sock),
+ %% State1 =
+ %% unlink_path(Path,
+ %% fun() ->
+ %% maps:remove(lsa, State)
+ %% end,
+ %% fun() -> State end),
+ %% {ok, maps:remove(lsock, State1)}
+ %% end},
+
+ %% %% *** We are done ***
+ %% ?SEV_FINISH_NORMAL
+ %% ],
+
+
+ %% ClientSeq =
+ %% [
+ %% %% *** Wait for start order part ***
+ %% #{desc => "await start",
+ %% cmd => fun(#{domain := local} = State) ->
+ %% {Tester, Path} = ?SEV_AWAIT_START(),
+ %% {ok, State#{tester => Tester,
+ %% server_path => Path}}
+ %% end},
+ %% #{desc => "monitor tester",
+ %% cmd => fun(#{tester := Tester} = _State) ->
+ %% _MRef = erlang:monitor(process, Tester),
+ %% ok
+ %% end},
+
+
+ %% %% *** Init part ***
+ %% #{desc => "which local address",
+ %% cmd => fun(#{domain := local = Domain,
+ %% server_path := Path} = State) ->
+ %% LSA = which_local_socket_addr(Domain),
+ %% SSA = #{family => Domain, path => Path},
+ %% {ok, State#{local_sa => LSA, server_sa => SSA}}
+ %% end},
+ %% #{desc => "create node",
+ %% cmd => fun(#{host := Host} = State) ->
+ %% ?SEV_IPRINT("try create node on ~p", [Host]),
+ %% case start_node(Host, client) of
+ %% {ok, Node} ->
+ %% ?SEV_IPRINT("client node ~p started",
+ %% [Node]),
+ %% {ok, State#{node => Node}};
+ %% {error, Reason} ->
+ %% {skip, Reason}
+ %% end
+ %% end},
+ %% #{desc => "monitor client node",
+ %% cmd => fun(#{node := Node} = _State) ->
+ %% true = erlang:monitor_node(Node, true),
+ %% ok
+ %% end},
+ %% #{desc => "start remote client on client node",
+ %% cmd => fun(#{node := Node} = State) ->
+ %% Pid = api_opt_sock_peercred_tcp_client_start(Node),
+ %% ?SEV_IPRINT("remote client ~p started", [Pid]),
+ %% {ok, State#{rclient => Pid}}
+ %% end},
+ %% #{desc => "monitor remote client",
+ %% cmd => fun(#{rclient := Pid}) ->
+ %% _MRef = erlang:monitor(process, Pid),
+ %% ok
+ %% end},
+ %% #{desc => "order remote client to start",
+ %% cmd => fun(#{rclient := Client,
+ %% proto := Proto,
+ %% server_sa := ServerSA}) ->
+ %% ?SEV_ANNOUNCE_START(Client, {Proto, ServerSA}),
+ %% ok
+ %% end},
+ %% #{desc => "await remote client ready",
+ %% cmd => fun(#{tester := Tester,
+ %% rclient := Client} = _State) ->
+ %% ?SEV_AWAIT_READY(Client, rclient, init,
+ %% [{tester, Tester}])
+ %% end},
+ %% #{desc => "announce ready (init)",
+ %% cmd => fun(#{tester := Tester}) ->
+ %% ?SEV_ANNOUNCE_READY(Tester, init),
+ %% ok
+ %% end},
+
+
+ %% %% The actual test
+ %% #{desc => "await continue (connect)",
+ %% cmd => fun(#{tester := Tester,
+ %% rclient := Client} = State) ->
+ %% case ?SEV_AWAIT_CONTINUE(Tester, tester, connect,
+ %% [{rclient, Client}]) of
+ %% {ok, {ConTimeout, ConLimit}} ->
+ %% {ok, State#{connect_timeout => ConTimeout,
+ %% connect_limit => ConLimit}};
+ %% {error, _} = ERROR ->
+ %% ERROR
+ %% end
+ %% end},
+ %% #{desc => "order remote client to continue (connect)",
+ %% cmd => fun(#{rclient := RClient,
+ %% connect_timeout := ConTimeout,
+ %% connect_limit := ConLimit}) ->
+ %% ?SEV_ANNOUNCE_CONTINUE(RClient, connect,
+ %% {ConTimeout, ConLimit}),
+ %% ok
+ %% end},
+ %% #{desc => "await remote client ready (connect)",
+ %% cmd => fun(#{tester := Tester,
+ %% rclient := RClient} = State) ->
+ %% case ?SEV_AWAIT_READY(RClient, rclient, connect,
+ %% [{tester, Tester}]) of
+ %% {ok, ok = _Result} ->
+ %% {ok, maps:remove(connect_limit, State)};
+ %% {ok, {error, {connect_limit_reached,R,L}}} ->
+ %% {skip,
+ %% ?LIB:f("Connect limit reached ~w: ~w",
+ %% [L, R])};
+ %% {ok, Result} ->
+ %% Result;
+ %% {error, _} = ERROR ->
+ %% ERROR
+ %% end
+ %% end},
+ %% #{desc => "announce ready (connect)",
+ %% cmd => fun(#{tester := Tester}) ->
+ %% ?SEV_ANNOUNCE_READY(Tester, connect),
+ %% ok
+ %% end},
+
+ %% %% Termination
+ %% #{desc => "await terminate (from tester)",
+ %% cmd => fun(#{tester := Tester,
+ %% rclient := RClient} = State) ->
+ %% case ?SEV_AWAIT_TERMINATE(Tester, tester,
+ %% [{rclient, RClient}]) of
+ %% ok ->
+ %% {ok, maps:remove(tester, State)};
+ %% {error, _} = ERROR ->
+ %% ERROR
+ %% end
+ %% end},
+ %% #{desc => "kill remote client",
+ %% cmd => fun(#{rclient := Client}) ->
+ %% ?SEV_ANNOUNCE_TERMINATE(Client),
+ %% ok
+ %% end},
+ %% #{desc => "await remote client termination",
+ %% cmd => fun(#{rclient := Client} = State) ->
+ %% ?SEV_AWAIT_TERMINATION(Client),
+ %% State1 = maps:remove(rclient, State),
+ %% {ok, State1}
+ %% end},
+ %% #{desc => "stop client node",
+ %% cmd => fun(#{node := Node} = _State) ->
+ %% stop_node(Node)
+ %% end},
+ %% #{desc => "await client node termination",
+ %% cmd => fun(#{node := Node} = State) ->
+ %% receive
+ %% {nodedown, Node} ->
+ %% State1 = maps:remove(node_id, State),
+ %% State2 = maps:remove(node, State1),
+ %% {ok, State2}
+ %% end
+ %% end},
+
+ %% %% *** We are done ***
+ %% ?SEV_FINISH_NORMAL
+ %% ],
+
+ %% TesterSeq =
+ %% [
+ %% %% *** Init part ***
+ %% #{desc => "monitor server",
+ %% cmd => fun(#{server := Server} = _State) ->
+ %% _MRef = erlang:monitor(process, Server),
+ %% ok
+ %% end},
+ %% #{desc => "monitor client",
+ %% cmd => fun(#{client := Client} = _State) ->
+ %% _MRef = erlang:monitor(process, Client),
+ %% ok
+ %% end},
+ %% #{desc => "which local address",
+ %% cmd => fun(#{domain := Domain} = State) ->
+ %% LSA = which_local_socket_addr(Domain),
+ %% {ok, State#{local_sa => LSA}}
+ %% end},
+ %% #{desc => "order server start",
+ %% cmd => fun(#{server := Server,
+ %% backlog := Backlog}) ->
+ %% ?SEV_ANNOUNCE_START(Server, Backlog),
+ %% ok
+ %% end},
+ %% #{desc => "await server ready (init)",
+ %% cmd => fun(#{server := Server, local_sa := LSA} = State) ->
+ %% {ok, Port} = ?SEV_AWAIT_READY(Server, server, init),
+ %% ServerSA = LSA#{port => Port},
+ %% {ok, State#{server_sa => ServerSA}}
+ %% end},
+ %% #{desc => "order client start",
+ %% cmd => fun(#{client := Client,
+ %% server_sa := ServerSA}) ->
+ %% ?SEV_ANNOUNCE_START(Client, ServerSA),
+ %% ok
+ %% end},
+ %% #{desc => "await client ready (init)",
+ %% cmd => fun(#{client := Client} = _State) ->
+ %% ?SEV_AWAIT_READY(Client, client, init),
+ %% ok
+ %% end},
+
+
+ %% %% The actual test
+ %% %% The server accepts the connect from the client, announces
+ %% %% this to us (accept) and then attempts to get peercred.
+ %% #{desc => "order client continue (connect)",
+ %% cmd => fun(#{client := Client,
+ %% timeout := Timeout,
+ %% connect_limit := ConLimit} = _State) ->
+ %% ?SEV_ANNOUNCE_CONTINUE(Client, connect,
+ %% {Timeout, ConLimit}),
+ %% ok
+ %% end},
+ %% #{desc => "await client ready (connect)",
+ %% cmd => fun(#{server := Server,
+ %% client := Client} = _State) ->
+ %% case ?SEV_AWAIT_READY(Client, client, connect,
+ %% [{server, Server}]) of
+ %% ok ->
+ %% ok;
+ %% {error, _} = ERROR ->
+ %% ERROR
+ %% end
+ %% end},
+ %% #{desc => "await server ready (accept)",
+ %% cmd => fun(#{server := Server,
+ %% client := Client} = _State) ->
+ %% case ?SEV_AWAIT_READY(Server, server, accept,
+ %% [{client, Client}]) of
+ %% ok ->
+ %% ok;
+ %% {error, _} = ERROR ->
+ %% ERROR
+ %% end
+ %% end},
+ %% #{desc => "await server ready (peercred)",
+ %% cmd => fun(#{server := Server,
+ %% client := Client} = _State) ->
+ %% case ?SEV_AWAIT_READY(Server, server, peercred,
+ %% [{client, Client}]) of
+ %% ok ->
+ %% ok;
+ %% {error, _} = ERROR ->
+ %% ERROR
+ %% end
+ %% end},
+
+
+ %% %% *** Terminate server ***
+ %% #{desc => "order client terminate",
+ %% cmd => fun(#{client := Client} = _State) ->
+ %% ?SEV_ANNOUNCE_TERMINATE(Client),
+ %% ok
+ %% end},
+ %% #{desc => "await client down",
+ %% cmd => fun(#{client := Client} = State) ->
+ %% ?SEV_AWAIT_TERMINATION(Client),
+ %% State1 = maps:remove(client, State),
+ %% {ok, State1}
+ %% end},
+ %% #{desc => "order server terminate",
+ %% cmd => fun(#{server := Server} = _State) ->
+ %% ?SEV_ANNOUNCE_TERMINATE(Server),
+ %% ok
+ %% end},
+ %% #{desc => "await server down",
+ %% cmd => fun(#{server := Server} = State) ->
+ %% ?SEV_AWAIT_TERMINATION(Server),
+ %% State1 = maps:remove(server, State),
+ %% State2 = maps:remove(server_sa, State1),
+ %% {ok, State2}
+ %% end},
+
+ %% %% *** We are done ***
+ %% ?SEV_FINISH_NORMAL
+ %% ],
+
+ %% i("create server evaluator"),
+ %% ServerInitState = #{domain => maps:get(domain, InitState)},
+ %% Server = ?SEV_START("server", ServerSeq, ServerInitState),
+
+ %% i("create client evaluator"),
+ %% ClientInitState = #{host => local_host(),
+ %% domain => maps:get(domain, InitState)},
+ %% Client = ?SEV_START("client", ClientSeq, ClientInitState),
+
+ %% i("create tester evaluator"),
+ %% TesterInitState = InitState#{server => Server#ev.pid,
+ %% client => Client#ev.pid},
+ %% Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ %% i("await evaluator(s)"),
+ %% ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]).
+
+
+ %% This should actually never be called (the conditions should cause a skip),
+ %% but just to be on the safe side...
+ skip.
+
+
+%% api_opt_sock_peercred_tcp_client_start(Node) ->
+%% Self = self(),
+%% Fun = fun() -> api_opt_sock_peercred_tcp_client(Self) end,
+%% erlang:spawn(Node, Fun).
+
+%% api_opt_sock_peercred_tcp_client(Parent) ->
+%% api_opt_sock_peercred_tcp_client_init(Parent),
+%% {Proto, ServerSA} = api_opt_sock_peercred_tcp_client_await_start(Parent),
+%% Domain = maps:get(family, ServerSA),
+%% api_opt_sock_peercred_tcp_client_announce_ready(Parent, init),
+%% api_opt_sock_peercred_tcp_client_await_continue(Parent, connect),
+%% Result = api_opt_sock_peercred_tcp_client_connect(Domain, Proto, ServerSA),
+%% ?SEV_IPRINT("result: ~p", [Result]),
+%% api_opt_sock_peercred_tcp_client_announce_ready(Parent, connect, Result),
+%% Reason = api_opt_sock_peercred_tcp_client_await_terminate(Parent),
+%% api_opt_sock_peercred_tcp_client_close(Result),
+%% exit(Reason).
+
+%% api_opt_sock_peercred_tcp_client_init(Parent) ->
+%% put(sname, "rclient"),
+%% _MRef = erlang:monitor(process, Parent),
+%% ok.
+
+%% api_opt_sock_peercred_tcp_client_await_start(Parent) ->
+%% ?SEV_AWAIT_START(Parent).
+
+%% api_opt_sock_peercred_tcp_client_announce_ready(Parent, Slogan) ->
+%% ?SEV_ANNOUNCE_READY(Parent, Slogan).
+%% api_opt_sock_peercred_tcp_client_announce_ready(Parent, Slogan, Result) ->
+%% ?SEV_ANNOUNCE_READY(Parent, Slogan, Result).
+
+%% api_opt_sock_peercred_tcp_client_await_continue(Parent, Slogan) ->
+%% case ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan) of
+%% ok ->
+%% ok;
+%% {ok, Extra} ->
+%% Extra;
+%% {error, Reason} ->
+%% exit({await_continue, Slogan, Reason})
+%% end.
+
+%% api_opt_sock_peercred_tcp_client_await_terminate(Parent) ->
+%% case ?SEV_AWAIT_TERMINATE(Parent, parent) of
+%% ok ->
+%% ok;
+%% {error, Reason} ->
+%% Reason
+%% end.
+
+%% api_opt_sock_peercred_tcp_client_connect(Domain, Proto, ServerSA) ->
+%% LSA = which_local_socket_addr(Domain),
+%% Sock = case socket:open(Domain, stream, Proto) of
+%% {ok, S} ->
+%% S;
+%% {error, OReason} ->
+%% ?FAIL({open, OReason})
+%% end,
+%% case socket:bind(Sock, LSA) of
+%% {ok, _} ->
+%% ok;
+%% {error, BReason} ->
+%% (catch socket:close(Sock)),
+%% ?FAIL({bind, BReason})
+%% end,
+%% case socket:connect(Sock, ServerSA) of
+%% ok ->
+%% {ok, Sock};
+%% {error, Reason} ->
+%% (catch socket:close(Sock)),
+%% ?FAIL({connect, Reason})
+%% end.
+
+%% api_opt_sock_peercred_tcp_client_close({ok, Sock}) ->
+%% (catch socket:close(Sock));
+%% api_opt_sock_peercred_tcp_client_close(_) ->
+%% ok.
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests the 'PRIORITY' socket 'socket' option with IPv4 UDP:
+%%
+%% socket:setopt(Sock, socket, priority, integer()).
+%%
+%%
+
+api_opt_sock_priority_udp4(suite) ->
+ [];
+api_opt_sock_priority_udp4(doc) ->
+ [];
+api_opt_sock_priority_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_sock_priority_udp4,
+ fun() -> has_support_sock_priority() end,
+ fun() ->
+ Set = fun(Sock, Value) ->
+ socket:setopt(Sock, socket, priority, Value)
+ end,
+ Get = fun(Sock) ->
+ socket:getopt(Sock, socket, priority)
+ end,
+ InitState = #{domain => inet,
+ type => dgram,
+ proto => udp,
+ set => Set,
+ get => Get},
+ ok = api_opt_sock_priority(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests the 'PRIORITY' socket 'socket' option with IPv4 TCP:
+%%
+%% socket:setopt(Sock, socket, priority, integer()).
+%%
+%%
+
+api_opt_sock_priority_tcp4(suite) ->
+ [];
+api_opt_sock_priority_tcp4(doc) ->
+ [];
+api_opt_sock_priority_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_sock_priority_tcp4,
+ fun() -> has_support_sock_priority() end,
+ fun() ->
+ Set = fun(Sock, Value) ->
+ socket:setopt(Sock, socket, priority, Value)
+ end,
+ Get = fun(Sock) ->
+ socket:getopt(Sock, socket, priority)
+ end,
+ InitState = #{domain => inet,
+ type => stream,
+ proto => tcp,
+ set => Set,
+ get => Get},
+ ok = api_opt_sock_priority(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_opt_sock_priority(InitState) ->
+ Seq =
+ [
+ #{desc => "local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa => LSA}}
+ end},
+
+ #{desc => "open socket",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ proto := Proto} = State) ->
+ Sock = sock_open(Domain, Type, Proto),
+ {ok, State#{sock => Sock}}
+ end},
+ #{desc => "bind",
+ cmd => fun(#{sock := Sock, lsa := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ?SEV_IPRINT("bound"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("bind failed: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "get current (default) priority",
+ cmd => fun(#{sock := Sock, get := Get} = State) ->
+ case Get(Sock) of
+ {ok, Prio} ->
+ ?SEV_IPRINT("(default) priority: ~p",
+ [Prio]),
+ {ok, State#{default => Prio}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed getting (default) "
+ "priority:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "change priority (to within non-root range)",
+ cmd => fun(#{sock := Sock,
+ default := DefaultPrio,
+ set := Set} = _State) ->
+ NewPrio =
+ if
+ (DefaultPrio =< 0) andalso
+ (DefaultPrio < 6) ->
+ DefaultPrio+1;
+ (DefaultPrio =:= 6) ->
+ DefaultPrio-1;
+ true ->
+ 3 % ...
+ end,
+ ?SEV_IPRINT("try set new priority (to ~p)",
+ [NewPrio]),
+ case Set(Sock, NewPrio) of
+ ok ->
+ ?SEV_IPRINT("priority changed (to ~p)",
+ [NewPrio]),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed setting timestamp:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "change priority (to outside root-range)",
+ cmd => fun(#{sock := Sock,
+ set := Set} = _State) ->
+ NewPrio = 42,
+ ?SEV_IPRINT("try set new priority (to ~p)",
+ [NewPrio]),
+ case Set(Sock, NewPrio) of
+ ok ->
+ ?SEV_IPRINT("priority changed (to ~p)",
+ [NewPrio]),
+ ok;
+ {error, eperm} ->
+ ?SEV_IPRINT("priority change not allowed"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed setting timestamp:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+ Evaluator = ?SEV_START("tester", Seq, InitState),
+ ok = ?SEV_AWAIT_FINISH([Evaluator]).
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests the 'SNDBUF' socket 'socket' option with IPv4 UDP:
+%%
+%% socket:setopt(Sock, socket, sndbuf, integer()).
+%%
+%%
+
+api_opt_sock_rcvbuf_udp4(suite) ->
+ [];
+api_opt_sock_rcvbuf_udp4(doc) ->
+ [];
+api_opt_sock_rcvbuf_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_sock_rcvbuf_udp4,
+ fun() -> has_support_sock_rcvbuf() end,
+ fun() ->
+ ok = api_opt_sock_buf_udp4(rcvbuf)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests the 'RCVBUF' socket 'socket' option with IPv4 UDP:
+%%
+%% socket:setopt(Sock, socket, rcvbuf, integer()).
+%%
+%%
+
+api_opt_sock_sndbuf_udp4(suite) ->
+ [];
+api_opt_sock_sndbuf_udp4(doc) ->
+ [];
+api_opt_sock_sndbuf_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_sock_sndbuf_udp4,
+ fun() -> has_support_sock_sndbuf() end,
+ fun() ->
+ ok = api_opt_sock_buf_udp4(sndbuf)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_opt_sock_buf_udp4(Opt) ->
+ Set = fun(Sock, Value) ->
+ socket:setopt(Sock, socket, Opt, Value)
+ end,
+ Get = fun(Sock) ->
+ socket:getopt(Sock, socket, Opt)
+ end,
+ InitState = #{domain => inet,
+ type => dgram,
+ proto => udp,
+ set => Set,
+ get => Get},
+ ok = api_opt_sock_buf(InitState).
+
+
+api_opt_sock_buf(InitState) ->
+ Seq =
+ [
+ #{desc => "local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa => LSA}}
+ end},
+
+ #{desc => "open socket",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ proto := Proto} = State) ->
+ Sock = sock_open(Domain, Type, Proto),
+ {ok, State#{sock => Sock}}
+ end},
+ #{desc => "bind",
+ cmd => fun(#{sock := Sock, lsa := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ?SEV_IPRINT("bound"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("bind failed: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "get current (default) buffer size",
+ cmd => fun(#{sock := Sock, get := Get} = State) ->
+ case Get(Sock) of
+ {ok, Sz} ->
+ ?SEV_IPRINT("(default) buffer: ~p",
+ [Sz]),
+ {ok, State#{default_sz => Sz}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed getting (default) "
+ "buffer size:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "change buffer size (default + 1024)",
+ cmd => fun(#{sock := Sock,
+ default_sz := DefaultSz,
+ set := Set} = State) ->
+ NewSz = DefaultSz + 1024,
+ ?SEV_IPRINT("try set new buffer size to ~w", [NewSz]),
+ case Set(Sock, NewSz) of
+ ok ->
+ ?SEV_IPRINT("Buffer size change success", []),
+ {ok, State#{new_sz => NewSz}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed changing buffer size:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "validate buffer change",
+ cmd => fun(#{sock := Sock,
+ get := Get,
+ new_sz := ExpSz} = _State) ->
+ ?SEV_IPRINT("try validate buffer size (~w)", [ExpSz]),
+ case Get(Sock) of
+ {ok, Sz} when (Sz >= ExpSz) ->
+ ?SEV_IPRINT("buffer size validated:"
+ "~n Sz: ~w (~w)", [Sz, ExpSz]),
+ ok;
+ {ok, Sz} ->
+ ?SEV_EPRINT("buffer size invalid:"
+ "~n Sz: ~w"
+ "~n Expected Sz: ~w", [Sz, ExpSz]),
+ {error, {invalid_size, Sz, ExpSz}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed get buffer size:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+ Evaluator = ?SEV_START("tester", Seq, InitState),
+ ok = ?SEV_AWAIT_FINISH([Evaluator]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests the 'RCVTIMEO' socket 'socket' option with IPv4 UDP:
+%%
+%% socket:setopt(Sock, socket, rcvtimeo, #{sec => integer(),
+%% usec => integer()}).
+%%
+%% We should really test that the receive behaves as expected,
+%% but we don't (we just set the value and read it back...)
+%%
+
+api_opt_sock_rcvtimeo_udp4(suite) ->
+ [];
+api_opt_sock_rcvtimeo_udp4(doc) ->
+ [];
+api_opt_sock_rcvtimeo_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_sock_rcvtimeo_udp4,
+ fun() -> has_support_sock_rcvtimeo() end,
+ fun() ->
+ ok = api_opt_sock_timeo_udp4(rcvtimeo)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests the 'SNDTIMEO' socket 'socket' option with IPv4 UDP:
+%%
+%% socket:setopt(Sock, socket, sndtimeo, integer()).
+%%
+%%
+
+api_opt_sock_sndtimeo_udp4(suite) ->
+ [];
+api_opt_sock_sndtimeo_udp4(doc) ->
+ [];
+api_opt_sock_sndtimeo_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_sock_sndtimeo_udp4,
+ fun() -> has_support_sock_sndtimeo() end,
+ fun() ->
+ ok = api_opt_sock_timeo_udp4(sndtimeo)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_opt_sock_timeo_udp4(Opt) ->
+ Set = fun(Sock, Value) ->
+ socket:setopt(Sock, socket, Opt, Value)
+ end,
+ Get = fun(Sock) ->
+ socket:getopt(Sock, socket, Opt)
+ end,
+ InitState = #{domain => inet,
+ type => dgram,
+ proto => udp,
+ opt => Opt,
+ set => Set,
+ get => Get},
+ ok = api_opt_sock_timeo(InitState).
+
+
+api_opt_sock_timeo(InitState) ->
+ Seq =
+ [
+ #{desc => "local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa => LSA}}
+ end},
+
+ #{desc => "open socket",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ proto := Proto} = State) ->
+ Sock = sock_open(Domain, Type, Proto),
+ {ok, State#{sock => Sock}}
+ end},
+ #{desc => "bind",
+ cmd => fun(#{sock := Sock, lsa := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ?SEV_IPRINT("bound"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("bind failed: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "get current (default) timeout",
+ cmd => fun(#{sock := Sock, get := Get} = State) ->
+ case Get(Sock) of
+ {ok, #{sec := _, usec := _} = TO} ->
+ ?SEV_IPRINT("(default) timeout: ~p", [TO]),
+ {ok, State#{default_timeo => TO}};
+ {error, enoprotoopt = Reason} ->
+ ?SEV_IPRINT("Failed getting (default) timeout:"
+ " ~p", [Reason]),
+ {skip, Reason};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed getting (default) timeout:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "change timeout",
+ cmd => fun(#{sock := Sock,
+ default_timeo := #{sec := DefaultSec} = DefaultTO,
+ set := Set} = State) ->
+ NewTO = DefaultTO#{sec => DefaultSec + 100},
+ ?SEV_IPRINT("try set new timeout to ~w", [NewTO]),
+ case Set(Sock, NewTO) of
+ ok ->
+ ?SEV_IPRINT("Timeout change success", []),
+ {ok, State#{new_timeo => NewTO}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed changing timeout:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "validate timeout change",
+ cmd => fun(#{sock := Sock,
+ get := Get,
+ new_timeo := #{sec := ExpSec} = ExpTO} = _State) ->
+ ?SEV_IPRINT("try validate timeout (~w)", [ExpTO]),
+ case Get(Sock) of
+ {ok, ExpTO} ->
+ ?SEV_IPRINT("timeout (exactly) validated"),
+ ok;
+ {ok, #{sec := Sec}} when (ExpSec =:= Sec) ->
+ %% For some reason OpenBSD "adjusts" the timeout,
+ %% so that usec does not (allways match)
+ ?SEV_IPRINT("timeout (approx) validated"),
+ ok;
+ {ok, TO} ->
+ ?SEV_EPRINT("timeout invalid:"
+ "~n Timeout: ~w"
+ "~n Expected Timeout: ~w",
+ [TO, ExpTO]),
+ {error, {invalid_timeo, TO, ExpTO}};
+ {error, edom = Reason} ->
+ %% On OpenBSD (at least) its possible that if the value
+ %% is too far "out of bounds", this will be the result:
+ %%
+ %% "Numerical argument out of domain"
+ %%
+ ?SEV_IPRINT("Failed get timeout:"
+ " ~p", [Reason]),
+ {skip, Reason};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed get timeout:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+ Evaluator = ?SEV_START("tester", Seq, InitState),
+ ok = ?SEV_AWAIT_FINISH([Evaluator]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests the 'RCVLOWAT' socket 'socket' option with IPv4 UDP:
+%%
+%% socket:setopt(Sock, socket, rcvlowat, integer()).
+%%
+%%
+
+api_opt_sock_rcvlowat_udp4(suite) ->
+ [];
+api_opt_sock_rcvlowat_udp4(doc) ->
+ [];
+api_opt_sock_rcvlowat_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_sock_rcvlowat_udp4,
+ fun() -> has_support_sock_rcvlowat() end,
+ fun() ->
+ ok = api_opt_sock_lowat_udp4(rcvlowat)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests the 'SNDLOWAT' socket 'socket' option with IPv4 UDP:
+%%
+%% socket:setopt(Sock, socket, sndlowat, integer()).
+%%
+%% This is (currently) not changeable on linux (among others),
+%% so we skip if we get ENOPROTOOPT when attempting a change.
+%%
+
+api_opt_sock_sndlowat_udp4(suite) ->
+ [];
+api_opt_sock_sndlowat_udp4(doc) ->
+ [];
+api_opt_sock_sndlowat_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_sock_sndlowat_udp4,
+ fun() -> has_support_sock_sndlowat() end,
+ fun() ->
+ ok = api_opt_sock_lowat_udp4(sndlowat)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_opt_sock_lowat_udp4(Opt) ->
+ Set = fun(Sock, Value) ->
+ socket:setopt(Sock, socket, Opt, Value)
+ end,
+ Get = fun(Sock) ->
+ socket:getopt(Sock, socket, Opt)
+ end,
+ InitState = #{domain => inet,
+ type => dgram,
+ proto => udp,
+ set => Set,
+ get => Get},
+ ok = api_opt_sock_lowat(InitState).
+
+
+api_opt_sock_lowat(InitState) ->
+ Seq =
+ [
+ #{desc => "local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa => LSA}}
+ end},
+
+ #{desc => "open socket",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ proto := Proto} = State) ->
+ Sock = sock_open(Domain, Type, Proto),
+ {ok, State#{sock => Sock}}
+ end},
+ #{desc => "bind",
+ cmd => fun(#{sock := Sock, lsa := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ?SEV_IPRINT("bound"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("bind failed: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "get current (default) lowat",
+ cmd => fun(#{sock := Sock, get := Get} = State) ->
+ case Get(Sock) of
+ {ok, LOWAT} ->
+ ?SEV_IPRINT("(default) lowat: ~p",
+ [LOWAT]),
+ {ok, State#{default_lowat => LOWAT}};
+ {error, enoprotoopt = Reason} ->
+ ?SEV_IPRINT("Failed getting (default) lowat:"
+ " ~p", [Reason]),
+ {skip, Reason};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed getting (default) lowat:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "change lowat ( + 1 )",
+ cmd => fun(#{sock := Sock,
+ default_lowat := DefaultLOWAT,
+ set := Set} = State) ->
+ NewLOWAT = DefaultLOWAT + 1,
+ ?SEV_IPRINT("try set new lowat to ~w", [NewLOWAT]),
+ case Set(Sock, NewLOWAT) of
+ ok ->
+ ?SEV_IPRINT("LOWAT change success", []),
+ {ok, State#{new_lowat => NewLOWAT}};
+ {error, enoprotoopt} ->
+ ?SEV_IPRINT("LOWAT not changeable", []),
+ {skip, "Not changeable"};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed changing buffer size:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "validate lowat",
+ cmd => fun(#{sock := Sock,
+ get := Get,
+ new_lowat := ExpLOWAT} = _State) ->
+ ?SEV_IPRINT("try validate lowat (~w)", [ExpLOWAT]),
+ case Get(Sock) of
+ {ok, ExpLOWAT} ->
+ ?SEV_IPRINT("lowat validated:"
+ "~n LOWAT: ~w", [ExpLOWAT]),
+ ok;
+ {ok, LOWAT} ->
+ ?SEV_EPRINT("lowat invalid:"
+ "~n LOWAT: ~w"
+ "~n Expected LOWAT: ~w",
+ [LOWAT, ExpLOWAT]),
+ {error, {invalid_lowat, LOWAT, ExpLOWAT}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed get lowat:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+ Evaluator = ?SEV_START("tester", Seq, InitState),
+ ok = ?SEV_AWAIT_FINISH([Evaluator]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests that the timestamp control message header is received when
+%% setting the socket 'socket' option true when using sendmsg/recvmsg
+%% on an IPv4 UDP (dgram) socket.
+%% So, this is done on the receiving side:
+%%
+%% socket:setopt(Sock, socket, timestamp, boolean()).
+%%
+%% All subsequent *received* messages will be timestamped.
+%%
+
+api_opt_sock_timestamp_udp4(suite) ->
+ [];
+api_opt_sock_timestamp_udp4(doc) ->
+ [];
+api_opt_sock_timestamp_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_sock_timestamp_udp4,
+ fun() -> has_support_sock_timestamp() end,
+ fun() ->
+ Set = fun(Sock, Value) ->
+ socket:setopt(Sock, socket, timestamp, Value)
+ end,
+ Get = fun(Sock) ->
+ socket:getopt(Sock, socket, timestamp)
+ end,
+ Send = fun(Sock, Data, Dest) ->
+ MsgHdr = #{addr => Dest,
+ iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock) of
+ {ok, #{addr := Source,
+ ctrl := CMsgHdrs,
+ iov := [Data]}} ->
+ {ok, {Source, CMsgHdrs, Data}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet,
+ proto => udp,
+ send => Send,
+ recv => Recv,
+ set => Set,
+ get => Get},
+ ok = api_opt_sock_timestamp_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_opt_sock_timestamp_udp(InitState) ->
+ Seq =
+ [
+ #{desc => "local address",
+ cmd => fun(#{domain := local = Domain} = State) ->
+ LSASrc = which_local_socket_addr(Domain),
+ LSADst = which_local_socket_addr(Domain),
+ {ok, State#{lsa_src => LSASrc,
+ lsa_dst => LSADst}};
+ (#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa_src => LSA,
+ lsa_dst => LSA}}
+ end},
+
+ #{desc => "open src socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ Sock = sock_open(Domain, dgram, Proto),
+ {ok, State#{sock_src => Sock}}
+ end},
+ #{desc => "bind src",
+ cmd => fun(#{sock_src := Sock, lsa_src := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ?SEV_IPRINT("src bound"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("src bind failed: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "sockname src socket",
+ cmd => fun(#{sock_src := Sock} = State) ->
+ SASrc = sock_sockname(Sock),
+ ?SEV_IPRINT("src sockaddr: "
+ "~n ~p", [SASrc]),
+ {ok, State#{sa_src => SASrc}}
+ end},
+ #{desc => "get current (default) timestamp for src socket",
+ cmd => fun(#{sock_src := Sock, get := Get} = _State) ->
+ case Get(Sock) of
+ {ok, false = Value} ->
+ ?SEV_IPRINT("src timestamp: ~p", [Value]),
+ ok;
+ {ok, Unexpected} ->
+ ?SEV_EPRINT("Unexpected src timestamp: ~p",
+ [Unexpected]),
+ {error, {unexpected, Unexpected}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed getting (default) timestamp:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "open dst socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ Sock = sock_open(Domain, dgram, Proto),
+ {ok, State#{sock_dst => Sock}}
+ end},
+ #{desc => "bind dst",
+ cmd => fun(#{sock_dst := Sock, lsa_dst := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ?SEV_IPRINT("src bound"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("src bind failed: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "sockname dst socket",
+ cmd => fun(#{sock_dst := Sock} = State) ->
+ SADst = sock_sockname(Sock),
+ ?SEV_IPRINT("dst sockaddr: "
+ "~n ~p", [SADst]),
+ {ok, State#{sa_dst => SADst}}
+ end},
+
+ #{desc => "send req (to dst) (WO TIMESTAMP)",
+ cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
+ Send(Sock, ?BASIC_REQ, Dst)
+ end},
+ #{desc => "recv req (from src)",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Src, [], ?BASIC_REQ}} ->
+ ok;
+ {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Recv Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Recv CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Recv Msg: ~p",
+ [Src, BadSrc,
+ [], BadCHdrs,
+ ?BASIC_REQ, BadReq]),
+ {error, {unexpected_data, UnexpData}};
+ {ok, UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Unexp Data: ~p",
+ [Src, [], ?BASIC_REQ, UnexpData]),
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+ #{desc => "send rep (to src)",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, send := Send}) ->
+ Send(Sock, ?BASIC_REP, Src)
+ end},
+ #{desc => "recv rep (from dst)",
+ cmd => fun(#{sock_src := Sock, sa_dst := Dst, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Dst, [], ?BASIC_REP}} ->
+ ok;
+ {ok, UnexpData} ->
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+
+
+ #{desc => "enable timestamp on dst socket",
+ cmd => fun(#{sock_dst := Sock, set := Set} = _State) ->
+ case Set(Sock, true) of
+ ok ->
+ ?SEV_IPRINT("dst timestamp enabled"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed setting timestamp:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+
+ #{desc => "send req 1 (to dst) (W TIMESTAMP)",
+ cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
+ Send(Sock, ?BASIC_REQ, Dst)
+ end},
+ #{desc => "recv req (from src)",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Src, [#{level := socket,
+ type := timestamp,
+ data := TS}], ?BASIC_REQ}} ->
+ ?SEV_IPRINT("received req *with* "
+ "expected timestamp: "
+ "~n ~p", [TS]),
+ ok;
+ {ok, {Src, [#{level := Level,
+ type := Type,
+ data := Data} = CMsgHdr], ?BASIC_REQ}} ->
+ ?SEV_EPRINT("Unexpected control message header:"
+ "~n Level: ~p"
+ "~n Type: ~p"
+ "~n Data: ~p",
+ [Level, Type, Data]),
+ {error, {unexpected_cmsghdr, CMsgHdr}};
+ {ok, {Src, CMsgHdrs, ?BASIC_REQ}} ->
+ ?SEV_EPRINT("Unexpected control message header(s):"
+ "~n CMsgHdrs: ~p",
+ [CMsgHdrs]),
+ {error, {unexpected_cmsghdrs, CMsgHdrs}};
+ {ok, UnexpData} ->
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+ #{desc => "send rep (to src)",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, send := Send}) ->
+ Send(Sock, ?BASIC_REP, Src)
+ end},
+ #{desc => "recv rep (from dst)",
+ cmd => fun(#{sock_src := Sock, sa_dst := Dst, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Dst, [], ?BASIC_REP}} ->
+ ok;
+ {ok, UnexpData} ->
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+
+
+ #{desc => "send req 2 (to dst) (W TIMESTAMP)",
+ cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
+ Send(Sock, ?BASIC_REQ, Dst)
+ end},
+ #{desc => "recv req (from src)",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Src, [#{level := socket,
+ type := timestamp,
+ data := TS}], ?BASIC_REQ}} ->
+ ?SEV_IPRINT("received req *with* "
+ "expected timestamp: "
+ "~n ~p", [TS]),
+ ok;
+ {ok, UnexpData} ->
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+ #{desc => "send rep (to src)",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, send := Send}) ->
+ Send(Sock, ?BASIC_REP, Src)
+ end},
+ #{desc => "recv rep (from dst)",
+ cmd => fun(#{sock_src := Sock, sa_dst := Dst, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Dst, [], ?BASIC_REP}} ->
+ ok;
+ {ok, UnexpData} ->
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+
+
+ #{desc => "disable timestamps on dst socket",
+ cmd => fun(#{sock_dst := Sock, set := Set} = _State) ->
+ case Set(Sock, false) of
+ ok ->
+ ?SEV_IPRINT("dst timestamp disabled"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed setting timestamp:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+
+ #{desc => "send req (to dst) (WO TIMESTAMP)",
+ cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
+ Send(Sock, ?BASIC_REQ, Dst)
+ end},
+ #{desc => "recv req (from src)",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Src, [], ?BASIC_REQ}} ->
+ ?SEV_IPRINT("received req *without* timestamp"),
+ ok;
+ {ok, UnexpData} ->
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+ #{desc => "send rep (to src)",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, send := Send}) ->
+ Send(Sock, ?BASIC_REP, Src)
+ end},
+ #{desc => "recv rep (from dst)",
+ cmd => fun(#{sock_src := Sock, sa_dst := Dst, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Dst, [], ?BASIC_REP}} ->
+ ok;
+ {ok, UnexpData} ->
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+
+
+ #{desc => "close src socket",
+ cmd => fun(#{domain := local,
+ sock_src := Sock,
+ lsa_src := #{path := Path}} = State) ->
+ ok = socket:close(Sock),
+ State1 =
+ unlink_path(Path,
+ fun() -> maps:remove(lsa_src, State) end,
+ fun() -> State end),
+ {ok, maps:remove(sock_src, State1)};
+ (#{sock_src := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock_src, State)}
+ end},
+ #{desc => "close dst socket",
+ cmd => fun(#{domain := local,
+ sock_dst := Sock,
+ lsa_dst := #{path := Path}} = State) ->
+ ok = socket:close(Sock),
+ State1 =
+ unlink_path(Path,
+ fun() -> maps:remove(lsa_dst, State) end,
+ fun() -> State end),
+ {ok, maps:remove(sock_dst, State1)};
+ (#{sock_dst := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock_dst, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+ Evaluator = ?SEV_START("tester", Seq, InitState),
+ ok = ?SEV_AWAIT_FINISH([Evaluator]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests that the timestamp control message header is received when
+%% setting the socket 'socket' option true when using sendmsg/recvmsg
+%% on an IPv4 TCP (stream) socket.
+%% So, this is done on the receiving side:
+%%
+%% socket:setopt(Sock, socket, timestamp, boolean()).
+%%
+%% All subsequent *received* messages will be timestamped.
+%%
+%% There is no mention of this not working for TCP in the man page
+%% on a SLES 11 SP4 machine (=> 3.0.101-108.87), but it does not
+%% (we don't get a timestamp control message header when its enabled).
+%% It also does not work on SLES 12 SP2 (=> 4.4.120-92.70),
+%% so we start by skipping from that version (4.4.120) or older!
+%% Don't actually know if its the distro or the (kernel) version...
+%%
+
+api_opt_sock_timestamp_tcp4(suite) ->
+ [];
+api_opt_sock_timestamp_tcp4(doc) ->
+ [];
+api_opt_sock_timestamp_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_sock_timestamp_tcp4,
+ fun() ->
+ has_support_sock_timestamp(),
+ is_good_enough_linux({4,4,120}),
+ is_not_freebsd(),
+ is_not_openbsd(),
+ is_not_netbsd(),
+ is_not_darwin()
+ end,
+ fun() ->
+ Set = fun(Sock, Value) ->
+ socket:setopt(Sock, socket, timestamp, Value)
+ end,
+ Get = fun(Sock) ->
+ socket:getopt(Sock, socket, timestamp)
+ end,
+ Send = fun(Sock, Data) ->
+ MsgHdr = #{iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock) of
+ {ok, #{ctrl := CMsgHdrs,
+ iov := [Data]}} ->
+ {ok, {CMsgHdrs, Data}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet,
+ proto => tcp,
+ send => Send,
+ recv => Recv,
+ set => Set,
+ get => Get},
+ ok = api_opt_sock_timestamp_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_opt_sock_timestamp_tcp(InitState) ->
+ process_flag(trap_exit, true),
+ ServerSeq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa => LSA}}
+ end},
+ #{desc => "create listen socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ case socket:open(Domain, stream, Proto) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{domain := local,
+ lsock := LSock,
+ lsa := LSA} = _State) ->
+ case socket:bind(LSock, LSA) of
+ {ok, _Port} ->
+ ok; % We do not care about the port for local
+ {error, _} = ERROR ->
+ ERROR
+ end;
+ (#{lsock := LSock, lsa := LSA} = State) ->
+ case sock_bind(LSock, LSA) of
+ {ok, Port} ->
+ ?SEV_IPRINT("bound to port: ~w", [Port]),
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{domain := local,
+ tester := Tester, lsa := #{path := Path}}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Path),
+ ok;
+ (#{tester := Tester, lport := Port}) ->
+ %% This is actually not used for unix domain socket
+ ?SEV_ANNOUNCE_READY(Tester, init, Port),
+ ok
+ end},
+
+
+ %% The actual test
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "await connection",
+ cmd => fun(#{lsock := LSock} = State) ->
+ case socket:accept(LSock) of
+ {ok, Sock} ->
+ ?SEV_IPRINT("accepted: ~n ~p", [Sock]),
+ {ok, State#{csock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept),
+ ok
+ end},
+
+ %% *** First message ***
+
+ #{desc => "await (recv) request 1",
+ cmd => fun(#{csock := Sock, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {[], ?BASIC_REQ}} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv request)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_req),
+ ok
+ end},
+ #{desc => "await continue (with send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply)
+ end},
+ #{desc => "send reply",
+ cmd => fun(#{csock := Sock, send := Send}) ->
+ Send(Sock, ?BASIC_REP)
+ end},
+ #{desc => "announce ready (send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_reply),
+ ok
+ end},
+
+ %% *** Second message ***
+
+ #{desc => "await (recv) request 2",
+ cmd => fun(#{csock := Sock, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {[], ?BASIC_REQ}} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv request)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_req),
+ ok
+ end},
+ #{desc => "await continue (with send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply)
+ end},
+ #{desc => "send reply",
+ cmd => fun(#{csock := Sock, send := Send}) ->
+ Send(Sock, ?BASIC_REP)
+ end},
+ #{desc => "announce ready (send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_reply),
+ ok
+ end},
+
+ %% *** Third message ***
+
+ #{desc => "await (recv) request 3",
+ cmd => fun(#{csock := Sock, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {[], ?BASIC_REQ}} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv request)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_req),
+ ok
+ end},
+ #{desc => "await continue (with send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply)
+ end},
+ #{desc => "send reply",
+ cmd => fun(#{csock := Sock, send := Send}) ->
+ Send(Sock, ?BASIC_REP)
+ end},
+ #{desc => "announce ready (send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_reply),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close connection socket",
+ cmd => fun(#{csock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(csock, State)}
+ end},
+ #{desc => "close listen socket",
+ cmd => fun(#{domain := local,
+ lsock := Sock,
+ local_sa := #{path := Path}} = State) ->
+ ok = socket:close(Sock),
+ State1 =
+ unlink_path(Path,
+ fun() ->
+ maps:remove(local_sa, State)
+ end,
+ fun() -> State end),
+ {ok, maps:remove(lsock, State1)};
+ (#{lsock := LSock} = State) ->
+ case socket:close(LSock) of
+ ok ->
+ {ok, maps:remove(lsock, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ ClientSeq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(#{domain := local} = State) ->
+ {Tester, Path} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, server_path => Path}};
+ (State) ->
+ {Tester, Port} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, server_port => Port}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** The init part ***
+ #{desc => "which server (local) address",
+ cmd => fun(#{domain := local = Domain,
+ server_path := Path} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ SSA = #{family => Domain, path => Path},
+ {ok, State#{local_sa => LSA, server_sa => SSA}};
+ (#{domain := Domain, server_port := Port} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ SSA = LSA#{port => Port},
+ {ok, State#{local_sa => LSA, server_sa => SSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ case socket:open(Domain, stream, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{sock := Sock, local_sa := LSA} = _State) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% *** The actual test ***
+ #{desc => "await continue (connect)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, connect)
+ end},
+ #{desc => "connect to server",
+ cmd => fun(#{sock := Sock, server_sa := SSA}) ->
+ socket:connect(Sock, SSA)
+ end},
+ #{desc => "announce ready (connect)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, connect),
+ ok
+ end},
+
+ %% *** First message (default=wo timestamp) ***
+
+ #{desc => "await continue (verify timestamp off)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, verify_timestamp)
+ end},
+ #{desc => "verify timestamp off",
+ cmd => fun(#{sock := Sock, get := Get} = _State) ->
+ case Get(Sock) of
+ {ok, false = _Value} ->
+ ?SEV_IPRINT("timestamp: ~p", [_Value]),
+ ok;
+ {ok, Unexpected} ->
+ ?SEV_EPRINT("Unexpected timestamp: ~p",
+ [Unexpected]),
+ {error, {unexpected_timestamp, Unexpected}};
+ {error, enoprotoopt = Reason} ->
+ {skip, Reason};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed getting timestamp:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (timestamp off)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, timestamp_off),
+ ok
+ end},
+
+ #{desc => "await continue (send request)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_req)
+ end},
+ #{desc => "send request 1 (to server)",
+ cmd => fun(#{sock := Sock, send := Send}) ->
+ Send(Sock, ?BASIC_REQ)
+ end},
+ #{desc => "announce ready (send request)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_req),
+ ok
+ end},
+ #{desc => "await recv reply 1 (from server, wo timestamp)",
+ cmd => fun(#{sock := Sock, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {[], ?BASIC_REP}} ->
+ ok;
+ {ok, {[], UnexpData}} ->
+ {error, {unexpected_reply_data, UnexpData}};
+ {ok, {BadCMsgHdrs, ?BASIC_REP}} ->
+ {error, {unexpected_reply_cmsghdrs,
+ BadCMsgHdrs}};
+ {ok, BadReply} ->
+ {error, {unexpected_reply, BadReply}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready 1 (recv reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_reply),
+ ok
+ end},
+
+ %% *** Second message (w timestamp) ***
+
+ #{desc => "await continue (enable timestamp)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, enable_timestamp)
+ end},
+ #{desc => "enable timestamp",
+ cmd => fun(#{sock := Sock, set := Set} = _State) ->
+ case Set(Sock, true) of
+ ok ->
+ ?SEV_IPRINT("timestamp enabled"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed enable timestamp:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+ %% Linux pecularity observed here...
+ %% Detected on Kernel 4.15.0-72 x96_64.
+ %% The option set to enable receiving timestamps just above
+ %% has failed to be effective down in "await recv reply 2
+ %% (from server, w timestamp)" below, unless we put the
+ %% sleep between setting the option and informing
+ %% the writer that it shall write to the other socket end.
+ %% A sleep 1 ms improves a lot but does not remove
+ %% problem completely. Believe it or not.
+ ?SEV_SLEEP(100),
+ #{desc => "announce ready (timestamp on)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, timestamp_on),
+ ok
+ end},
+
+ #{desc => "await continue (send request 2)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_req)
+ end},
+ #{desc => "send request 2 (to server)",
+ cmd => fun(#{sock := Sock, send := Send}) ->
+ Send(Sock, ?BASIC_REQ)
+ end},
+ #{desc => "announce ready (send request 2)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_req),
+ ok
+ end},
+ #{desc => "await recv reply 2 (from server, w timestamp)",
+ cmd => fun(#{sock := Sock, recv := Recv, get := Get}) ->
+ case Recv(Sock) of
+ {ok, {[#{level := socket,
+ type := timestamp,
+ data := TS}], ?BASIC_REP}} ->
+ ?SEV_IPRINT("received reply *with* "
+ "expected timestamp: "
+ "~n ~p", [TS]),
+ ok;
+ {ok, {[#{level := socket,
+ type := timestamp,
+ data := UTS}], BadData}} ->
+ ?SEV_EPRINT("received reply *with* "
+ "unexpected timestamp:"
+ "~n ~p"
+ "Current timestamp value:"
+ "~n ~p",
+ [UTS, Get(Sock)]),
+ {error, {unexpected_reply_data, BadData}};
+ {ok, {BadCMsgHdrs, ?BASIC_REP}} ->
+ ?SEV_EPRINT("received reply *with* "
+ "unexpected cmsg headers:"
+ "~n ~p"
+ "Current timestamp value: "
+ "~n ~p",
+ [BadCMsgHdrs, Get(Sock)]),
+ {error, {unexpected_reply_cmsghdrs,
+ BadCMsgHdrs}};
+ {ok, BadReply} ->
+ {error, {unexpected_reply, BadReply}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready 2 (recv reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_reply),
+ ok
+ end},
+
+ %% *** Third message (wo timestamp) ***
+
+ #{desc => "await continue (disable timestamp)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, disable_timestamp)
+ end},
+ #{desc => "disable timestamp",
+ cmd => fun(#{sock := Sock, set := Set} = _State) ->
+ case Set(Sock, false) of
+ ok ->
+ ?SEV_IPRINT("timestamp disabled"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed disable timestamp:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (timestamp off)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, timestamp_off),
+ ok
+ end},
+
+ #{desc => "await continue (send request 3)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_req)
+ end},
+ #{desc => "send request 3 (to server)",
+ cmd => fun(#{sock := Sock, send := Send}) ->
+ Send(Sock, ?BASIC_REQ)
+ end},
+ #{desc => "announce ready (send request 3)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_req),
+ ok
+ end},
+ #{desc => "await recv reply 3 (from server, wo timestamp)",
+ cmd => fun(#{sock := Sock, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {[], ?BASIC_REP}} ->
+ ?SEV_IPRINT("received reply *without* "
+ "timestamp"),
+ ok;
+ {ok, {BadCMsgHdrs, ?BASIC_REP}} ->
+ {error, {unexpected_reply_cmsghdrs,
+ BadCMsgHdrs}};
+ {ok, {[], BadData}} ->
+ {error, {unexpected_reply_data,
+ BadData}};
+ {ok, BadReply} ->
+ {error, {unexpected_reply, BadReply}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready 3 (recv reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_reply),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{domain := local,
+ sock := Sock,
+ local_sa := #{path := Path}} = State) ->
+ ok = socket:close(Sock),
+ State1 =
+ unlink_path(Path,
+ fun() ->
+ maps:remove(local_sa, State)
+ end,
+ fun() -> State end),
+ {ok, maps:remove(sock, State1)};
+ (#{sock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = State) ->
+ {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{server_port => Port}}
+ end},
+
+ %% Start the client
+ #{desc => "order client start",
+ cmd => fun(#{client := Pid, server_port := Port} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Port),
+ ok
+ end},
+ #{desc => "await client ready (init)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client, init)
+ end},
+
+ %% *** The actual test ***
+ #{desc => "order server to continue (with accept)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, accept),
+ ok
+ end},
+%%% ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order client to continue (with connect)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, connect),
+ ok
+ end},
+ #{desc => "await client ready (connect)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, connect)
+ end},
+ #{desc => "await server ready (accept)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, accept)
+ end},
+
+ %% *** First message (default=wo timestamp) ***
+
+ #{desc => "order client to continue (with verify timestamp off)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, verify_timestamp),
+ ok
+ end},
+ #{desc => "await client ready (timestamp off)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, timestamp_off)
+ end},
+
+ #{desc => "order client to continue (with send request 1)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send_req),
+ ok
+ end},
+ #{desc => "await client ready (with send request 1)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, send_req)
+ end},
+ #{desc => "await server ready (request recv 1)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv_req)
+ end},
+ #{desc => "order server to continue (with send reply)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, send_reply),
+ ok
+ end},
+ #{desc => "await server ready (with reply sent)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, send_reply)
+ end},
+ #{desc => "await client ready (reply recv)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, recv_reply)
+ end},
+
+ %% Second message (w timestamp)
+
+ #{desc => "order client to continue (with enable timestamp)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, enable_timestamp),
+ ok
+ end},
+ #{desc => "await client ready (timestamp on)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, timestamp_on)
+ end},
+
+ #{desc => "order client to continue (with send request 2)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send_req),
+ ok
+ end},
+ #{desc => "await client ready (with send request 2)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, send_req)
+ end},
+ #{desc => "await server ready (request recv 2)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv_req)
+ end},
+ #{desc => "order server to continue (with send reply 2)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, send_reply),
+ ok
+ end},
+ #{desc => "await server ready (with reply sent 2)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, send_reply)
+ end},
+ #{desc => "await client ready (reply recv 2)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, recv_reply)
+ end},
+
+ %% Third message (wo timestamp)
+
+ #{desc => "order client to continue (with disable timestamp)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, disable_timestamp),
+ ok
+ end},
+ #{desc => "await client ready (timestamp off)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, timestamp_off)
+ end},
+
+ #{desc => "order client to continue (with send request 3)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send_req),
+ ok
+ end},
+ #{desc => "await client ready (with send request 3)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, send_req)
+ end},
+ #{desc => "await server ready (request recv 3)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv_req)
+ end},
+ #{desc => "order server to continue (with send reply 3)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, send_reply),
+ ok
+ end},
+ #{desc => "await server ready (with reply sent 3)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, send_reply)
+ end},
+ #{desc => "await client ready (reply recv 3)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, recv_reply)
+ end},
+
+
+ %% *** Termination ***
+ #{desc => "order client to terminate",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await client termination",
+ cmd => fun(#{client := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(client, State),
+ {ok, State1}
+ end},
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Server),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Server} = State) ->
+ ?SEV_AWAIT_TERMINATION(Server),
+ State1 = maps:remove(server, State),
+ {ok, State1}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ i("start server evaluator"),
+ Server = ?SEV_START("server", ServerSeq, InitState),
+
+ i("start client evaluator"),
+ Client = ?SEV_START("client", ClientSeq, InitState),
+
+ i("start tester evaluator"),
+ TesterInitState = #{server => Server#ev.pid,
+ client => Client#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests that the add_mambership and drop_membership ip options work.
+%% We create one server and two clients. The server only send messages,
+%% the clients only receives messages.
+%% An UDP datagram is forbidden (RFC 1122) from having a source address
+%% that is a multicast address (or a broadcast address).
+%% So, the server create a socket "for sending" and the clients sockets
+%% "for receiving".
+%% Sending socket: Bound to the local address (and any port).
+%% When sending, the dest will be the multicast address
+%% and port of the receiving socket.
+%% Receiving socket: Bound to the multicast address and port.
+api_opt_ip_add_drop_membership(suite) ->
+ [];
+api_opt_ip_add_drop_membership(doc) ->
+ ["OTP-15908 (ERL-980)"];
+api_opt_ip_add_drop_membership(_Config) when is_list(_Config) ->
+ ?TT(?SECS(30)),
+ tc_try(api_opt_ip_add_drop_membership,
+ fun() ->
+ has_support_ip_add_membership(),
+ has_support_ip_drop_membership(),
+ has_support_ip_multicast()
+ end,
+ fun() -> api_opt_ip_add_drop_membership() end).
+
+
+api_opt_ip_add_drop_membership() ->
+ Set = fun(S, Key, Val) ->
+ socket:setopt(S, ip, Key, Val)
+ end,
+ AddMembership = fun(S, Val) -> Set(S, add_membership, Val) end,
+ DropMembership = fun(S, Val) -> Set(S, drop_membership, Val) end,
+
+ ServerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ {Tester, MSA} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, msa => MSA}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, dgram, udp) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make recv socket reuse addr",
+ cmd => fun(#{sock := Sock} = _State) ->
+ case socket:setopt(Sock, socket, reuseaddr, true) of
+ ok ->
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed set reuseaddr: "
+ "~n ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "bind recv socket to multicast address",
+ cmd => fun(#{sock := Sock, msa := MSA} = State) ->
+ case sock_bind(Sock, MSA) of
+ {ok, Port} ->
+ ?SEV_IPRINT("bound to:"
+ "~n ~p", [Port]),
+ {ok, State#{msa => MSA#{port => Port}}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (add_membership)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, add_membership)
+ end},
+ #{desc => "add membership",
+ cmd => fun(#{sock := Sock,
+ msa := #{addr := MAddr},
+ local_sa := #{addr := Addr}} = State) ->
+ MReq = #{multiaddr => MAddr,
+ interface => Addr},
+ ?SEV_IPRINT("try add membership to:"
+ "~n ~p", [MReq]),
+ case AddMembership(Sock, MReq) of
+ ok ->
+ ?SEV_IPRINT("membership added"),
+ {ok, State#{mreq => MReq}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed adding membership to: "
+ "~n ~p"
+ "~n Reason: ~p",
+ [MReq, Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (add-membership)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, add_membership),
+ ok
+ end},
+
+ #{desc => "await continue (drop_membership)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, drop_membership)
+ end},
+ #{desc => "drop membership",
+ cmd => fun(#{sock := Sock,
+ mreq := MReq} = State) ->
+ ?SEV_IPRINT("try drop membership from:"
+ "~n ~p", [MReq]),
+ case DropMembership(Sock, MReq) of
+ ok ->
+ ?SEV_IPRINT("membership dropped"),
+ {ok, maps:remove(mreq, State)};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed drop membership from: "
+ "~n ~p"
+ "~n Reason: ~p",
+ [MReq, Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (drop-membership)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, drop_membership),
+ ok
+ end},
+
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Server} = _State) ->
+ _MRef = erlang:monitor(process, Server),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid, msa := MSA} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, MSA),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = _State) ->
+ case ?SEV_AWAIT_READY(Pid, server, init) of
+ ok ->
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Start of server failed: "
+ "~n ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+
+ %% *** The actual test ***
+ #{desc => "order server to continue (add-membership)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, add_membership),
+ ok
+ end},
+ #{desc => "await server ready (add-membership)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, add_membership)
+ end},
+
+ #{desc => "order server to continue (drop-membership)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, drop_membership),
+ ok
+ end},
+ #{desc => "await server ready (drop-membership)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, drop_membership)
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ %% *** Termination ***
+ #{desc => "order server terminate",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Server),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Server} = State) ->
+ ?SEV_AWAIT_TERMINATION(Server),
+ {ok, maps:remove(server, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ Domain = inet,
+ i("get multicast address"),
+ MAddr = which_ip_multicast_address(),
+ MSA = #{family => Domain, addr => MAddr},
+
+ i("start server evaluator"),
+ ServerInitState = #{domain => Domain},
+ Server = ?SEV_START("server", ServerSeq, ServerInitState),
+
+ i("start tester evaluator"),
+ TesterInitState = #{domain => Domain,
+ msa => MSA,
+ server => Server#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator(s)"),
+ ok = ?SEV_AWAIT_FINISH([Tester, Server]).
+
+
+
+which_ip_multicast_address() ->
+ which_multicast_address(inet).
+
+which_multicast_address(Domain) ->
+ case os:type() of
+ {unix, linux} ->
+ WhichMAddr = fun([_, _, MAddr]) -> MAddr end,
+ which_multicast_address2(Domain, WhichMAddr);
+
+ {unix, sunos} ->
+ WhichMAddr = fun([_, MAddr, _]) -> MAddr end,
+ which_multicast_address2(Domain, WhichMAddr);
+
+ Type ->
+ %% Actually, what is "not supported". is netstat!
+ not_supported({multicast, Type})
+ end.
+
+%% Note that the 'netstat -g' table looks different on linux and SunOS
+%% Linux: IfName - RefCnt - Group
+%% SunOS: IfName - Group - RefCnt
+
+which_multicast_address2(Domain, WhichMAddr) ->
+ IfName = which_local_host_ifname(Domain),
+ %% On some platforms the netstat barfs out some crap on stderr
+ %% before the actual info...
+ case os:cmd("netstat -g 2>/dev/null | grep " ++ IfName) of
+ [] ->
+ %% Can't figure out if we support multicast or not...
+ not_supported(no_netstat);
+ NetstatGroupsStr ->
+ try
+ begin
+ NetstatGroups0 = string:tokens(NetstatGroupsStr, [$\n]),
+ NetstatGroups = [string:tokens(G, [$ ]) ||
+ G <- NetstatGroups0],
+ MAddrs = [WhichMAddr(NetstatGroup) ||
+ NetstatGroup <- NetstatGroups],
+ which_multicast_address3(Domain, MAddrs)
+ end
+ catch
+ throw:E:_ ->
+ throw(E);
+ C:E:S ->
+ not_supported({multicast, {C,E,S}})
+ end
+ end.
+
+which_multicast_address3(_Domain, []) ->
+ not_supported({multicast, no_valid_addrs});
+which_multicast_address3(Domain, [MAddrStr|MAddrs]) ->
+ %% Even on linux some of these are not actually addresses, but
+ %% "host names", such as all-systems.mcast.net. But both
+ %% address strings, such as "224.0.0.251" and host name strings
+ %% gets translated into an address by the inet:inet:getaddr/2.
+ case inet:getaddr(MAddrStr, Domain) of
+ {ok, MAddr} ->
+ MAddr;
+ {error, _} ->
+ which_multicast_address3(Domain, MAddrs)
+ end.
+
+which_local_host_ifname(Domain) ->
+ case ?LIB:which_local_host_info(Domain) of
+ {ok, #{name := Name}} ->
+ Name;
+ {error, Reason} ->
+ not_supported({multicast, Reason})
+ end.
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests that the pktinfo control message header is received when
+%% setting the socket 'ip' option pktinfo is set to true when using
+%% sendmsg/recvmsg on an IPv4 UDP (dgram) socket.
+%% So, this is done on the receiving side:
+%%
+%% socket:setopt(Sock, ip, pktinfo, boolean()).
+%%
+%% For all subsequent *received* messages, the pktinfo control message
+%% header will be with the message.
+%%
+%% Note that it *should* be possible to explicitly send pktinfo also,
+%% but this have not yet been implemented (in socket), so that part
+%% we do not test!!
+%%
+
+api_opt_ip_pktinfo_udp4(suite) ->
+ [];
+api_opt_ip_pktinfo_udp4(doc) ->
+ [];
+api_opt_ip_pktinfo_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_ip_pktinfo_udp4,
+ fun() -> has_support_ip_pktinfo() end,
+ fun() ->
+ Set = fun(Sock, Value) ->
+ socket:setopt(Sock, ip, pktinfo, Value)
+ end,
+ Get = fun(Sock) ->
+ socket:getopt(Sock, ip, pktinfo)
+ end,
+ Send = fun(Sock, Data, Dest, default) ->
+ MsgHdr = #{addr => Dest,
+ iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr);
+ (Sock, Data, Dest, Info) ->
+ %% We do not support this at the moment!!!
+ CMsgHdr = #{level => ip,
+ type => pktinfo,
+ data => Info},
+ MsgHdr = #{addr => Dest,
+ ctrl => [CMsgHdr],
+ iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock) of
+ {ok, #{addr := Source,
+ ctrl := CMsgHdrs,
+ iov := [Data]}} ->
+ {ok, {Source, CMsgHdrs, Data}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet,
+ proto => udp,
+ send => Send,
+ recv => Recv,
+ set => Set,
+ get => Get},
+ ok = api_opt_ip_pktinfo_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_opt_ip_pktinfo_udp(InitState) ->
+ Seq =
+ [
+ #{desc => "local address",
+ cmd => fun(#{domain := local = Domain} = State) ->
+ LSASrc = which_local_socket_addr(Domain),
+ LSADst = which_local_socket_addr(Domain),
+ {ok, State#{lsa_src => LSASrc,
+ lsa_dst => LSADst}};
+ (#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa_src => LSA,
+ lsa_dst => LSA}}
+ end},
+
+ #{desc => "open src socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ Sock = sock_open(Domain, dgram, Proto),
+ {ok, State#{sock_src => Sock}}
+ end},
+ #{desc => "bind src",
+ cmd => fun(#{sock_src := Sock, lsa_src := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ?SEV_IPRINT("src bound"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("src bind failed: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "sockname src socket",
+ cmd => fun(#{sock_src := Sock} = State) ->
+ SASrc = sock_sockname(Sock),
+ ?SEV_IPRINT("src sockaddr: "
+ "~n ~p", [SASrc]),
+ {ok, State#{sa_src => SASrc}}
+ end},
+
+ #{desc => "open dst socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ Sock = sock_open(Domain, dgram, Proto),
+ {ok, State#{sock_dst => Sock}}
+ end},
+ #{desc => "bind dst",
+ cmd => fun(#{sock_dst := Sock, lsa_dst := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ?SEV_IPRINT("src bound"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("src bind failed: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "sockname dst socket",
+ cmd => fun(#{sock_dst := Sock} = State) ->
+ SADst = sock_sockname(Sock),
+ ?SEV_IPRINT("dst sockaddr: "
+ "~n ~p", [SADst]),
+ {ok, State#{sa_dst => SADst}}
+ end},
+ #{desc => "default pktinfo for dst socket",
+ cmd => fun(#{sock_dst := Sock, get := Get} = _State) ->
+ case Get(Sock) of
+ {ok, false = Value} ->
+ ?SEV_IPRINT("dst recvttl: ~p", [Value]),
+ ok;
+ {ok, Unexpected} ->
+ ?SEV_EPRINT("Unexpected src recvtos: ~p",
+ [Unexpected]),
+ {error, {unexpected, Unexpected}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed getting (default) timestamp:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "send req (to dst) (wo (explicit) pktinfo)",
+ cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
+ Send(Sock, ?BASIC_REQ, Dst, default)
+ end},
+ #{desc => "recv req (from src)",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Src, [], ?BASIC_REQ}} ->
+ ok;
+ {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Recv Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Recv CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Recv Msg: ~p",
+ [Src, BadSrc,
+ [], BadCHdrs,
+ ?BASIC_REQ, BadReq]),
+ {error, {unexpected_data, UnexpData}};
+ {ok, UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Unexp Data: ~p",
+ [Src, [], ?BASIC_REQ, UnexpData]),
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+
+
+ %% *** We do not *yet* support sending pktinfo ***
+
+ %% #{desc => "send req (to dst) (w explicit pktinfo)",
+ %% cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
+ %% Send(Sock, ?BASIC_REQ, Dst, PktInfo)
+ %% end},
+ %% #{desc => "recv req (from src) - wo pktinfo",
+ %% cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
+ %% case Recv(Sock) of
+ %% {ok, {Src, [], ?BASIC_REQ}} ->
+ %% ok;
+ %% {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} ->
+ %% ?SEV_EPRINT("Unexpected msg: "
+ %% "~n Expect Source: ~p"
+ %% "~n Recv Source: ~p"
+ %% "~n Expect CHdrs: ~p"
+ %% "~n Recv CHdrs: ~p"
+ %% "~n Expect Msg: ~p"
+ %% "~n Recv Msg: ~p",
+ %% [Src, BadSrc,
+ %% [], BadCHdrs,
+ %% ?BASIC_REQ, BadReq]),
+ %% {error, {unexpected_data, UnexpData}};
+ %% {ok, UnexpData} ->
+ %% ?SEV_EPRINT("Unexpected msg: "
+ %% "~n Expect Source: ~p"
+ %% "~n Expect CHdrs: ~p"
+ %% "~n Expect Msg: ~p"
+ %% "~n Unexp Data: ~p",
+ %% [Src, [], ?BASIC_REQ, UnexpData]),
+ %% {error, {unexpected_data, UnexpData}};
+ %% {error, _} = ERROR ->
+ %% %% At the moment there is no way to get
+ %% %% status or state for the socket...
+ %% ERROR
+ %% end
+ %% end},
+
+ #{desc => "enable pktinfo on dst socket",
+ cmd => fun(#{sock_dst := Sock, set := Set} = _State) ->
+ case Set(Sock, true) of
+ ok ->
+ ?SEV_IPRINT("dst pktinfo enabled"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed setting pktinfo:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "send req (to dst) (wo explicit pktinfo)",
+ cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
+ Send(Sock, ?BASIC_REQ, Dst, default)
+ end},
+ #{desc => "recv req (from src) - w default pktinfo",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Src, [#{level := ip,
+ type := pktinfo,
+ data := #{addr := Addr,
+ ifindex := IfIdx,
+ spec_dst := SpecDst}}],
+ ?BASIC_REQ}} ->
+ ?SEV_IPRINT("Got (default) Pkt Info: "
+ "~n Addr: ~p"
+ "~n If Index: ~p"
+ "~n Spec Dst: ~p",
+ [Addr, IfIdx, SpecDst]),
+ ok;
+ {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Recv Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Recv CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Recv Msg: ~p",
+ [Src, BadSrc,
+ [], BadCHdrs,
+ ?BASIC_REQ, BadReq]),
+ {error, {unexpected_data, UnexpData}};
+ {ok, UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Unexp Data: ~p",
+ [Src, [], ?BASIC_REQ, UnexpData]),
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+
+
+ %% *** We do not *yet* support sending pktinfo ***
+
+ %% #{desc => "send req (to dst) (w explicit pktinfo)",
+ %% cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
+ %% Send(Sock, ?BASIC_REQ, Dst, PktInfo)
+ %% end},
+ %% #{desc => "recv req (from src) - w ttl = 100",
+ %% cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
+ %% case Recv(Sock) of
+ %% {ok, {Src, [#{level := ip,
+ %% type := ttl,
+ %% data := PktInfo}], ?BASIC_REQ}} ->
+ %% ?SEV_IPRINT("Got Pkt Info: "
+ %% "~n ~p", [Info]),
+ %% ok;
+ %% {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} ->
+ %% ?SEV_EPRINT("Unexpected msg: "
+ %% "~n Expect Source: ~p"
+ %% "~n Recv Source: ~p"
+ %% "~n Expect CHdrs: ~p"
+ %% "~n Recv CHdrs: ~p"
+ %% "~n Expect Msg: ~p"
+ %% "~n Recv Msg: ~p",
+ %% [Src, BadSrc,
+ %% [], BadCHdrs,
+ %% ?BASIC_REQ, BadReq]),
+ %% {error, {unexpected_data, UnexpData}};
+ %% {ok, UnexpData} ->
+ %% ?SEV_EPRINT("Unexpected msg: "
+ %% "~n Expect Source: ~p"
+ %% "~n Expect CHdrs: ~p"
+ %% "~n Expect Msg: ~p"
+ %% "~n Unexp Data: ~p",
+ %% [Src, [], ?BASIC_REQ, UnexpData]),
+ %% {error, {unexpected_data, UnexpData}};
+ %% {error, _} = ERROR ->
+ %% %% At the moment there is no way to get
+ %% %% status or state for the socket...
+ %% ERROR
+ %% end
+ %% end},
+
+ #{desc => "close src socket",
+ cmd => fun(#{sock_src := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock_src, State)}
+ end},
+ #{desc => "close dst socket",
+ cmd => fun(#{sock_dst := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock_dst, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+ Evaluator = ?SEV_START("tester", Seq, InitState),
+ ok = ?SEV_AWAIT_FINISH([Evaluator]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests that the options control message header is received when
+%% setting the socket 'ip' option recvopts is set to true when using
+%% sendmsg/recvmsg on an IPv4 UDP (dgram) socket.
+%% So, this is done on the receiving side:
+%%
+%% socket:setopt(Sock, ip, recvopts, boolean()).
+%%
+%% For all subsequent *received* messages, the options control message
+%% header will be with the message.
+%%
+%% Note that it *should* be possible to explicitly send options also,
+%% but this have not yet been implemented (in socket), so that part
+%% we do not test!!
+%%
+%%
+%% <NOTE>
+%%
+%% This test does not currently work. The recvopts is supposed to
+%% result in a IP_OPTIONS control message header but does not!
+%% So, exactly how we are suppose to use this option is unknown.
+%% So, let the test code remain, but skip until we have figured out
+%% how to test this.
+%%
+%% </NOTE>
+%%
+
+api_opt_ip_recvopts_udp4(suite) ->
+ [];
+api_opt_ip_recvopts_udp4(doc) ->
+ [];
+api_opt_ip_recvopts_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_ip_recvopts_udp4,
+ fun() ->
+ has_support_ip_recvopts(),
+ %% We also use the recvtos and timestamp options
+ %% in this test, so at least one of them must
+ %% be supported
+ has_support_ip_recvtos_and_or_sock_timestamp(),
+ not_yet_implemented()
+
+ end,
+ fun() ->
+ Set = fun(Sock, Value) ->
+ socket:setopt(Sock, ip, recvopts, Value)
+ end,
+ Get = fun(Sock) ->
+ socket:getopt(Sock, ip, recvopts)
+ end,
+ Send = fun(Sock, Data, Dest, default) ->
+ MsgHdr = #{addr => Dest,
+ iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr);
+ (Sock, Data, Dest, Info) ->
+ %% We do not support this at the moment!!!
+ CMsgHdr = #{level => ip,
+ type => options,
+ data => Info},
+ MsgHdr = #{addr => Dest,
+ ctrl => [CMsgHdr],
+ iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock) of
+ {ok, #{addr := Source,
+ ctrl := CMsgHdrs,
+ iov := [Data]}} ->
+ {ok, {Source, CMsgHdrs, Data}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet,
+ proto => udp,
+ send => Send,
+ recv => Recv,
+ set => Set,
+ get => Get},
+ ok = api_opt_ip_recvopts_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_opt_ip_recvopts_udp(InitState) ->
+ Seq =
+ [
+ %% Start by figure out which of the ip:recvtos and/or socket:timestamp
+ %% options we can use.
+ #{desc => "test for ip:recvtos",
+ cmd => fun(State) ->
+ ?SEV_IPRINT("test for ip:recvtos"),
+ case socket:is_supported(options, ip, recvtos) of
+ true ->
+ ?SEV_IPRINT("use ip:recvtos"),
+ {ok, State#{recvtos => true}};
+ false ->
+ ?SEV_IPRINT("do *not* use ip:recvtos"),
+ {ok, State#{recvtos => false}}
+ end
+ end},
+ #{desc => "test for socket:timestamp",
+ cmd => fun(State) ->
+ ?SEV_IPRINT("test for socket:timestamp"),
+ case socket:is_supported(options, socket, timestamp) of
+ true ->
+ ?SEV_IPRINT("use socket:timestamp"),
+ {ok, State#{timestamp => true}};
+ false ->
+ ?SEV_IPRINT("do *not* use socket:timestamp"),
+ {ok, State#{timestamp => false}}
+ end
+ end},
+
+ #{desc => "local address",
+ cmd => fun(#{domain := local = Domain} = State) ->
+ LSASrc = which_local_socket_addr(Domain),
+ LSADst = which_local_socket_addr(Domain),
+ {ok, State#{lsa_src => LSASrc,
+ lsa_dst => LSADst}};
+ (#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa_src => LSA,
+ lsa_dst => LSA}}
+ end},
+
+ #{desc => "open src socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ Sock = sock_open(Domain, dgram, Proto),
+ {ok, State#{sock_src => Sock}}
+ end},
+ #{desc => "bind src",
+ cmd => fun(#{sock_src := Sock, lsa_src := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ?SEV_IPRINT("src bound"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("src bind failed: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "sockname src socket",
+ cmd => fun(#{sock_src := Sock} = State) ->
+ SASrc = sock_sockname(Sock),
+ ?SEV_IPRINT("src sockaddr: "
+ "~n ~p", [SASrc]),
+ {ok, State#{sa_src => SASrc}}
+ end},
+
+ #{desc => "open dst socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ Sock = sock_open(Domain, dgram, Proto),
+ {ok, State#{sock_dst => Sock}}
+ end},
+ #{desc => "bind dst",
+ cmd => fun(#{sock_dst := Sock, lsa_dst := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ?SEV_IPRINT("src bound"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("src bind failed: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "sockname dst socket",
+ cmd => fun(#{sock_dst := Sock} = State) ->
+ SADst = sock_sockname(Sock),
+ ?SEV_IPRINT("dst sockaddr: "
+ "~n ~p", [SADst]),
+ {ok, State#{sa_dst => SADst}}
+ end},
+ #{desc => "default recvopts for dst socket",
+ cmd => fun(#{sock_dst := Sock, get := Get} = _State) ->
+ case Get(Sock) of
+ {ok, false = Value} ->
+ ?SEV_IPRINT("dst recvopts: ~p", [Value]),
+ ok;
+ {ok, Unexpected} ->
+ ?SEV_EPRINT("Unexpected src recvtos: ~p",
+ [Unexpected]),
+ {error, {unexpected, Unexpected}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed getting (default) timestamp:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "send req (to dst) (wo (explicit) options)",
+ cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
+ Send(Sock, ?BASIC_REQ, Dst, default)
+ end},
+ #{desc => "recv req (from src)",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Src, [], ?BASIC_REQ}} ->
+ ok;
+ {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Recv Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Recv CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Recv Msg: ~p",
+ [Src, BadSrc,
+ [], BadCHdrs,
+ ?BASIC_REQ, BadReq]),
+ {error, {unexpected_data, UnexpData}};
+ {ok, UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Unexp Data: ~p",
+ [Src, [], ?BASIC_REQ, UnexpData]),
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+
+
+ %% *** We do not *yet* support sending options ***
+
+ %% #{desc => "send req (to dst) (w explicit options)",
+ %% cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
+ %% Send(Sock, ?BASIC_REQ, Dst, Opts)
+ %% end},
+ %% #{desc => "recv req (from src) - wo options",
+ %% cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
+ %% case Recv(Sock) of
+ %% {ok, {Src, [], ?BASIC_REQ}} ->
+ %% ok;
+ %% {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} ->
+ %% ?SEV_EPRINT("Unexpected msg: "
+ %% "~n Expect Source: ~p"
+ %% "~n Recv Source: ~p"
+ %% "~n Expect CHdrs: ~p"
+ %% "~n Recv CHdrs: ~p"
+ %% "~n Expect Msg: ~p"
+ %% "~n Recv Msg: ~p",
+ %% [Src, BadSrc,
+ %% [], BadCHdrs,
+ %% ?BASIC_REQ, BadReq]),
+ %% {error, {unexpected_data, UnexpData}};
+ %% {ok, UnexpData} ->
+ %% ?SEV_EPRINT("Unexpected msg: "
+ %% "~n Expect Source: ~p"
+ %% "~n Expect CHdrs: ~p"
+ %% "~n Expect Msg: ~p"
+ %% "~n Unexp Data: ~p",
+ %% [Src, [], ?BASIC_REQ, UnexpData]),
+ %% {error, {unexpected_data, UnexpData}};
+ %% {error, _} = ERROR ->
+ %% %% At the moment there is no way to get
+ %% %% status or state for the socket...
+ %% ERROR
+ %% end
+ %% end},
+
+ #{desc => "enable recvopts on dst socket",
+ cmd => fun(#{sock_dst := Sock, set := Set} = _State) ->
+ case Set(Sock, true) of
+ ok ->
+ ?SEV_IPRINT("dst recvopts enabled"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed setting recvopts:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ %% This specific option, recvtos, is tested in another test case
+ %% Note that this may not actually be supported here!!
+ #{desc => "maybe enable ip:recvtos on dst socket",
+ cmd => fun(#{recvtos := true, sock_dst := Sock} = _State) ->
+ ?SEV_IPRINT("enable ip:recvtos"),
+ ok = socket:setopt(Sock, ip, recvtos, true);
+ (#{recvtos := false} = _State) ->
+ ok
+ end},
+ %% This specific option, timestamp, is tested in another test case
+ #{desc => "maybe enable socket:timestamp on dst socket",
+ cmd => fun(#{timestamp := true, sock_dst := Sock} = _State) ->
+ ?SEV_IPRINT("enable socket:timestamp"),
+ ok = socket:setopt(Sock, socket, timestamp, true);
+ (#{timestamp := false} = _State) ->
+ ok
+ end},
+
+ #{desc => "send req (to dst) (wo explicit options)",
+ cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
+ Send(Sock, ?BASIC_REQ, Dst, default)
+ end},
+ #{desc => "recv req (from src) - w default options",
+ cmd => fun(#{recvtos := true, timestamp := true,
+ sock_dst := Sock,
+ sa_src := Src,
+ recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Src, Opts, ?BASIC_REQ}}
+ when (length(Opts) =:= 2) ->
+ ?SEV_IPRINT("Got (default) Options: "
+ "~n Opts: ~p", [Opts]),
+ ok;
+ {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Recv Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Recv CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Recv Msg: ~p",
+ [Src, BadSrc,
+ [], BadCHdrs,
+ ?BASIC_REQ, BadReq]),
+ {error, {unexpected_data, UnexpData}};
+ {ok, UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Unexp Data: ~p",
+ [Src, [], ?BASIC_REQ, UnexpData]),
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end;
+ (#{timestamp := true,
+ sock_dst := Sock,
+ sa_src := Src,
+ recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Src, Opts, ?BASIC_REQ}}
+ when (length(Opts) =:= 1) ->
+ ?SEV_IPRINT("Got (default) Options: "
+ "~n Opts: ~p", [Opts]),
+ ok;
+ {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Recv Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Recv CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Recv Msg: ~p",
+ [Src, BadSrc,
+ [], BadCHdrs,
+ ?BASIC_REQ, BadReq]),
+ {error, {unexpected_data, UnexpData}};
+ {ok, UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Unexp Data: ~p",
+ [Src, [], ?BASIC_REQ, UnexpData]),
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end;
+ (#{recvtos := true,
+ sock_dst := Sock,
+ sa_src := Src,
+ recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Src, Opts, ?BASIC_REQ}}
+ when (length(Opts) =:= 1) ->
+ ?SEV_IPRINT("Got (default) Options: "
+ "~n Opts: ~p", [Opts]),
+ ok;
+ {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Recv Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Recv CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Recv Msg: ~p",
+ [Src, BadSrc,
+ [], BadCHdrs,
+ ?BASIC_REQ, BadReq]),
+ {error, {unexpected_data, UnexpData}};
+ {ok, UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Unexp Data: ~p",
+ [Src, [], ?BASIC_REQ, UnexpData]),
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+
+
+ %% *** We do not *yet* support sending options ***
+
+ %% #{desc => "send req (to dst) (w explicit options)",
+ %% cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
+ %% Send(Sock, ?BASIC_REQ, Dst, Opts)
+ %% end},
+ %% #{desc => "recv req (from src) - w options",
+ %% cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
+ %% case Recv(Sock) of
+ %% {ok, {Src, Opts, ?BASIC_REQ}} ->
+ %% ?SEV_IPRINT("Got Options: "
+ %% "~n ~p", [Opts]),
+ %% ok;
+ %% {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} ->
+ %% ?SEV_EPRINT("Unexpected msg: "
+ %% "~n Expect Source: ~p"
+ %% "~n Recv Source: ~p"
+ %% "~n Expect CHdrs: ~p"
+ %% "~n Recv CHdrs: ~p"
+ %% "~n Expect Msg: ~p"
+ %% "~n Recv Msg: ~p",
+ %% [Src, BadSrc,
+ %% [], BadCHdrs,
+ %% ?BASIC_REQ, BadReq]),
+ %% {error, {unexpected_data, UnexpData}};
+ %% {ok, UnexpData} ->
+ %% ?SEV_EPRINT("Unexpected msg: "
+ %% "~n Expect Source: ~p"
+ %% "~n Expect CHdrs: ~p"
+ %% "~n Expect Msg: ~p"
+ %% "~n Unexp Data: ~p",
+ %% [Src, [], ?BASIC_REQ, UnexpData]),
+ %% {error, {unexpected_data, UnexpData}};
+ %% {error, _} = ERROR ->
+ %% %% At the moment there is no way to get
+ %% %% status or state for the socket...
+ %% ERROR
+ %% end
+ %% end},
+
+ #{desc => "close src socket",
+ cmd => fun(#{sock_src := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock_src, State)}
+ end},
+ #{desc => "close dst socket",
+ cmd => fun(#{sock_dst := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock_dst, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+ Evaluator = ?SEV_START("tester", Seq, InitState),
+ ok = ?SEV_AWAIT_FINISH([Evaluator]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests that the origdstaddr control message header is received when
+%% setting the socket 'ip' option recvorigdstaddr is set to true when
+%% using sendmsg/recvmsg on an IPv4 UDP (dgram) socket.
+%% So, this is done on the receiving side:
+%%
+%% socket:setopt(Sock, ip, recvorigdstaddr, boolean()).
+%%
+%% For all subsequent *received* messages, the origdstaddr control
+%% message header will be with the message.
+%%
+%%
+
+api_opt_ip_recvorigdstaddr_udp4(suite) ->
+ [];
+api_opt_ip_recvorigdstaddr_udp4(doc) ->
+ [];
+api_opt_ip_recvorigdstaddr_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_ip_recvorigdstaddr_udp4,
+ fun() -> has_support_ip_recvorigdstaddr() end,
+ fun() ->
+ Set = fun(Sock, Value) ->
+ socket:setopt(Sock, ip, recvorigdstaddr, Value)
+ end,
+ Get = fun(Sock) ->
+ socket:getopt(Sock, ip, recvorigdstaddr)
+ end,
+ Send = fun(Sock, Data, Dest) ->
+ MsgHdr = #{addr => Dest,
+ iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock) of
+ {ok, #{addr := Source,
+ ctrl := CMsgHdrs,
+ iov := [Data]}} ->
+ {ok, {Source, CMsgHdrs, Data}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet,
+ proto => udp,
+ send => Send,
+ recv => Recv,
+ set => Set,
+ get => Get},
+ ok = api_opt_ip_recvorigdstaddr_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_opt_ip_recvorigdstaddr_udp(InitState) ->
+ Seq =
+ [
+ #{desc => "local address",
+ cmd => fun(#{domain := local = Domain} = State) ->
+ LSASrc = which_local_socket_addr(Domain),
+ LSADst = which_local_socket_addr(Domain),
+ {ok, State#{lsa_src => LSASrc,
+ lsa_dst => LSADst}};
+ (#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa_src => LSA,
+ lsa_dst => LSA}}
+ end},
+
+ #{desc => "open src socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ Sock = sock_open(Domain, dgram, Proto),
+ {ok, State#{sock_src => Sock}}
+ end},
+ #{desc => "bind src",
+ cmd => fun(#{sock_src := Sock, lsa_src := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ?SEV_IPRINT("src bound"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("src bind failed: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "sockname src socket",
+ cmd => fun(#{sock_src := Sock} = State) ->
+ SASrc = sock_sockname(Sock),
+ ?SEV_IPRINT("src sockaddr: "
+ "~n ~p", [SASrc]),
+ {ok, State#{sa_src => SASrc}}
+ end},
+
+ #{desc => "open dst socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ Sock = sock_open(Domain, dgram, Proto),
+ {ok, State#{sock_dst => Sock}}
+ end},
+ #{desc => "bind dst",
+ cmd => fun(#{sock_dst := Sock, lsa_dst := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ?SEV_IPRINT("src bound"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("src bind failed: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "sockname dst socket",
+ cmd => fun(#{sock_dst := Sock} = State) ->
+ SADst = sock_sockname(Sock),
+ ?SEV_IPRINT("dst sockaddr: "
+ "~n ~p", [SADst]),
+ {ok, State#{sa_dst => SADst}}
+ end},
+ #{desc => "get default recvorigdstaddr for dst socket",
+ cmd => fun(#{sock_dst := Sock, get := Get} = _State) ->
+ case Get(Sock) of
+ {ok, false = Value} ->
+ ?SEV_IPRINT("dst recvorigdstaddr: ~p", [Value]),
+ ok;
+ {ok, Unexpected} ->
+ ?SEV_EPRINT("Unexpected src recvorigdstaddr: ~p",
+ [Unexpected]),
+ {error, {unexpected, Unexpected}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed getting (default) "
+ "recvorigdstaddr:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "send req (to dst) (when recvorigdstaddr disabled)",
+ cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
+ Send(Sock, ?BASIC_REQ, Dst)
+ end},
+ #{desc => "recv req (from src) - wo origdstaddr",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Src, [], ?BASIC_REQ}} ->
+ ok;
+ {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Recv Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Recv CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Recv Msg: ~p",
+ [Src, BadSrc,
+ [], BadCHdrs,
+ ?BASIC_REQ, BadReq]),
+ {error, {unexpected_data, UnexpData}};
+ {ok, UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Unexp Data: ~p",
+ [Src, [], ?BASIC_REQ, UnexpData]),
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+
+ #{desc => "enable recvorigdstaddr on dst socket",
+ cmd => fun(#{sock_dst := Sock, set := Set} = _State) ->
+ case Set(Sock, true) of
+ ok ->
+ ?SEV_IPRINT("dst recvorigdstaddr enabled"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed enable recvorigdstaddr:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "send req (to dst) (when recvorigdstaddr enabled)",
+ cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
+ Send(Sock, ?BASIC_REQ, Dst)
+ end},
+ #{desc => "recv req (from src) - w origdstaddr",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Src, [#{level := ip,
+ type := origdstaddr,
+ data := Addr}], ?BASIC_REQ}} ->
+ ?SEV_IPRINT("got origdstaddr "
+ "control message header: "
+ "~n ~p", [Addr]),
+ ok;
+ {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Recv Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Recv CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Recv Msg: ~p",
+ [Src, BadSrc,
+ [#{level => ip,
+ type => origdstaddr,
+ data => "something"}], BadCHdrs,
+ ?BASIC_REQ, BadReq]),
+ {error, {unexpected_data, UnexpData}};
+ {ok, UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Unexp Data: ~p",
+ [Src,
+ [#{level => ip,
+ type => origdstaddr,
+ data => "something"}],
+ ?BASIC_REQ, UnexpData]),
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+
+ #{desc => "close src socket",
+ cmd => fun(#{sock_src := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock_src, State)}
+ end},
+ #{desc => "close dst socket",
+ cmd => fun(#{sock_dst := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock_dst, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+ Evaluator = ?SEV_START("tester", Seq, InitState),
+ ok = ?SEV_AWAIT_FINISH([Evaluator]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests that the tos control message header is received when
+%% setting the socket 'ip' option recvtos is set to true when using
+%% sendmsg/recvmsg on an IPv4 UDP (dgram) socket.
+%% So, this is done on the receiving side:
+%%
+%% socket:setopt(Sock, ip, recvtos, boolean()).
+%%
+%% For all subsequent *received* messages, the tos control message
+%% header will be with the message.
+%%
+%% On some platforms it works sending TOS with the message (sendmsg with
+%% a control message header), but since its not universal, we can't use
+%% that method. Instead, set tos (true) on the sending socket.
+%%
+
+api_opt_ip_recvtos_udp4(suite) ->
+ [];
+api_opt_ip_recvtos_udp4(doc) ->
+ [];
+api_opt_ip_recvtos_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_ip_recvtos_udp4,
+ fun() -> has_support_ip_recvtos() end,
+ fun() ->
+ Set = fun(Sock, Value) ->
+ socket:setopt(Sock, ip, recvtos, Value)
+ end,
+ Get = fun(Sock) ->
+ socket:getopt(Sock, ip, recvtos)
+ end,
+ Send = fun(Sock, Data, Dest, default) ->
+ MsgHdr = #{addr => Dest,
+ iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr);
+ (Sock, Data, Dest, TOS) ->
+ CMsgHdr = #{level => ip,
+ type => tos,
+ data => TOS},
+ MsgHdr = #{addr => Dest,
+ ctrl => [CMsgHdr],
+ iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock) of
+ {ok, #{addr := Source,
+ ctrl := CMsgHdrs,
+ iov := [Data]}} ->
+ {ok, {Source, CMsgHdrs, Data}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet,
+ proto => udp,
+ send => Send,
+ recv => Recv,
+ set => Set,
+ get => Get},
+ ok = api_opt_ip_recvtos_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_opt_ip_recvtos_udp(InitState) ->
+ Seq =
+ [
+ #{desc => "local address",
+ cmd => fun(#{domain := local = Domain} = State) ->
+ LSASrc = which_local_socket_addr(Domain),
+ LSADst = which_local_socket_addr(Domain),
+ {ok, State#{lsa_src => LSASrc,
+ lsa_dst => LSADst}};
+ (#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa_src => LSA,
+ lsa_dst => LSA}}
+ end},
+
+ #{desc => "open src socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ Sock = sock_open(Domain, dgram, Proto),
+ {ok, State#{sock_src => Sock}}
+ end},
+ #{desc => "bind src",
+ cmd => fun(#{sock_src := Sock, lsa_src := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ?SEV_IPRINT("src bound"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("src bind failed: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "sockname src socket",
+ cmd => fun(#{sock_src := Sock} = State) ->
+ SASrc = sock_sockname(Sock),
+ ?SEV_IPRINT("src sockaddr: "
+ "~n ~p", [SASrc]),
+ {ok, State#{sa_src => SASrc}}
+ end},
+
+ #{desc => "open dst socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ Sock = sock_open(Domain, dgram, Proto),
+ {ok, State#{sock_dst => Sock}}
+ end},
+ #{desc => "bind dst",
+ cmd => fun(#{sock_dst := Sock, lsa_dst := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ?SEV_IPRINT("src bound"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("src bind failed: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "sockname dst socket",
+ cmd => fun(#{sock_dst := Sock} = State) ->
+ SADst = sock_sockname(Sock),
+ ?SEV_IPRINT("dst sockaddr: "
+ "~n ~p", [SADst]),
+ {ok, State#{sa_dst => SADst}}
+ end},
+ #{desc => "default recvtos for dst socket",
+ cmd => fun(#{sock_dst := Sock, get := Get} = _State) ->
+ case Get(Sock) of
+ {ok, false = Value} ->
+ ?SEV_IPRINT("dst recvtos: ~p", [Value]),
+ ok;
+ {ok, Unexpected} ->
+ ?SEV_EPRINT("Unexpected src recvtos: ~p",
+ [Unexpected]),
+ {error, {unexpected, Unexpected}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed getting (default) timestamp:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "send req (to dst) (wo explicit tos)",
+ cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
+ Send(Sock, ?BASIC_REQ, Dst, default)
+ end},
+ #{desc => "recv req (from src)",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Src, [], ?BASIC_REQ}} ->
+ ok;
+ {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Recv Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Recv CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Recv Msg: ~p",
+ [Src, BadSrc,
+ [], BadCHdrs,
+ ?BASIC_REQ, BadReq]),
+ {error, {unexpected_data, UnexpData}};
+ {ok, UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Unexp Data: ~p",
+ [Src, [], ?BASIC_REQ, UnexpData]),
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+
+ #{desc => "set tos = mincost on src sock",
+ cmd => fun(#{sock_src := Sock}) ->
+ ok = socket:setopt(Sock, ip, tos, mincost)
+ end},
+ #{desc => "send req (to dst) (w tos = mincost)",
+ cmd => fun(#{sock_src := Sock,
+ sa_dst := Dst,
+ send := Send}) ->
+ Send(Sock, ?BASIC_REQ, Dst, default)
+ end},
+
+ %% #{desc => "send req (to dst) (w explicit tos = mincost)",
+ %% cmd => fun(#{sock_src := Sock,
+ %% sa_dst := Dst,
+ %% send := Send}) ->
+ %% socket:setopt(Sock, otp, debug, true),
+ %% case Send(Sock, ?BASIC_REQ, Dst, mincost) of
+ %% ok ->
+ %% socket:setopt(Sock, otp, debug, false),
+ %% ok;
+ %% {error, Reason} ->
+ %% ?SEV_EPRINT("Failed sending message with tos: "
+ %% "~n Reason: ~p", [Reason]),
+ %% socket:setopt(Sock, otp, debug, false),
+ %% {skip, "Failed sending message with TOS"}
+ %% end
+ %% end},
+
+ #{desc => "recv req (from src) - wo explicit tos",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Src, [], ?BASIC_REQ}} ->
+ ok;
+ {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Recv Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Recv CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Recv Msg: ~p",
+ [Src, BadSrc,
+ [], BadCHdrs,
+ ?BASIC_REQ, BadReq]),
+ {error, {unexpected_data, UnexpData}};
+ {ok, UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Unexp Data: ~p",
+ [Src, [], ?BASIC_REQ, UnexpData]),
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+
+ #{desc => "set tos = 0 on src sock (\"disabled\")",
+ cmd => fun(#{sock_src := Sock}) ->
+ ok = socket:setopt(Sock, ip, tos, 0)
+ end},
+
+ #{desc => "enable recvtos on dst socket",
+ cmd => fun(#{sock_dst := Sock, set := Set} = _State) ->
+ case Set(Sock, true) of
+ ok ->
+ ?SEV_IPRINT("dst recvtos enabled"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed setting recvtos:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "send req (to dst) (wo explicit tos)",
+ cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
+ Send(Sock, ?BASIC_REQ, Dst, default)
+ end},
+ #{desc => "recv req (from src) - w default tos",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Src, [#{level := ip,
+ type := TOS,
+ data := 0}], ?BASIC_REQ}}
+ when ((TOS =:= tos) orelse (TOS =:= recvtos)) ->
+ ?SEV_IPRINT("got default TOS (~w) "
+ "control message header", [TOS]),
+ ok;
+ {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Recv Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Recv CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Recv Msg: ~p",
+ [Src, BadSrc,
+ [], BadCHdrs,
+ ?BASIC_REQ, BadReq]),
+ {error, {unexpected_data, UnexpData}};
+ {ok, UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Unexp Data: ~p",
+ [Src, [], ?BASIC_REQ, UnexpData]),
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+
+ #{desc => "set tos = mincost on src sock",
+ cmd => fun(#{sock_src := Sock}) ->
+ ok = socket:setopt(Sock, ip, tos, mincost)
+ end},
+
+ #{desc => "send req (to dst) (w tos = mincost)",
+ cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
+ Send(Sock, ?BASIC_REQ, Dst, default)
+ end},
+ #{desc => "recv req (from src) - w tos = mincost",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Src, [#{level := ip,
+ type := TOS,
+ data := mincost = TOSData}],
+ ?BASIC_REQ}}
+ when ((TOS =:= tos) orelse (TOS =:= recvtos)) ->
+ ?SEV_IPRINT("got expected TOS (~w) = ~w "
+ "control message header",
+ [TOS, TOSData]),
+ ok;
+ {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Recv Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Recv CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Recv Msg: ~p",
+ [Src, BadSrc,
+ [], BadCHdrs,
+ ?BASIC_REQ, BadReq]),
+ {error, {unexpected_data, UnexpData}};
+ {ok, UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Unexp Data: ~p",
+ [Src, [], ?BASIC_REQ, UnexpData]),
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+
+ #{desc => "close src socket",
+ cmd => fun(#{sock_src := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock_src, State)}
+ end},
+ #{desc => "close dst socket",
+ cmd => fun(#{sock_dst := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock_dst, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+ Evaluator = ?SEV_START("tester", Seq, InitState),
+ ok = ?SEV_AWAIT_FINISH([Evaluator]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests that the ttl control message header is received when
+%% setting the socket 'ip' option recvttl is set to true when using
+%% sendmsg/recvmsg on an IPv4 UDP (dgram) socket.
+%% So, this is done on the receiving side:
+%%
+%% socket:setopt(Sock, ip, recvttl, boolean()).
+%%
+%% For all subsequent *received* messages, the ttl control message
+%% header will be with the message.
+%%
+%% On darwin we don't actually get the TTL we send even after we have
+%% enabled TTL. Instead we get the default value (which was 64).
+%% Possibly this is because we run the test in the same OS process and
+%% even the same erlang process....
+%% The same issue on OpenBSD (6.6).
+%% Maybe we should send and receive from different VMs, until then
+%% skip darwin and OpenBSD.
+%%
+
+api_opt_ip_recvttl_udp4(suite) ->
+ [];
+api_opt_ip_recvttl_udp4(doc) ->
+ [];
+api_opt_ip_recvttl_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_ip_recvttl_udp4,
+ fun() ->
+ has_support_ip_recvttl(),
+ is_not_openbsd(),
+ is_not_darwin()
+ end,
+ fun() ->
+ Set = fun(Sock, Value) ->
+ socket:setopt(Sock, ip, recvttl, Value)
+ end,
+ Get = fun(Sock) ->
+ socket:getopt(Sock, ip, recvttl)
+ end,
+ Send = fun(Sock, Data, Dest, default) ->
+ MsgHdr = #{addr => Dest,
+ iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr);
+ (Sock, Data, Dest, TTL) ->
+ CMsgHdr = #{level => ip,
+ type => ttl,
+ data => TTL},
+ MsgHdr = #{addr => Dest,
+ ctrl => [CMsgHdr],
+ iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock) of
+ {ok, #{addr := Source,
+ ctrl := CMsgHdrs,
+ iov := [Data]}} ->
+ {ok, {Source, CMsgHdrs, Data}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet,
+ proto => udp,
+ send => Send,
+ recv => Recv,
+ set => Set,
+ get => Get},
+ ok = api_opt_ip_recvttl_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_opt_ip_recvttl_udp(InitState) ->
+ Seq =
+ [
+ #{desc => "local address",
+ cmd => fun(#{domain := local = Domain} = State) ->
+ LSASrc = which_local_socket_addr(Domain),
+ LSADst = which_local_socket_addr(Domain),
+ {ok, State#{lsa_src => LSASrc,
+ lsa_dst => LSADst}};
+ (#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa_src => LSA,
+ lsa_dst => LSA}}
+ end},
+
+ #{desc => "open src socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ Sock = sock_open(Domain, dgram, Proto),
+ {ok, State#{sock_src => Sock}}
+ end},
+ #{desc => "bind src",
+ cmd => fun(#{sock_src := Sock, lsa_src := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ?SEV_IPRINT("src bound"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("src bind failed: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "sockname src socket",
+ cmd => fun(#{sock_src := Sock} = State) ->
+ SASrc = sock_sockname(Sock),
+ ?SEV_IPRINT("src sockaddr: "
+ "~n ~p", [SASrc]),
+ {ok, State#{sa_src => SASrc}}
+ end},
+
+ #{desc => "open dst socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ Sock = sock_open(Domain, dgram, Proto),
+ {ok, State#{sock_dst => Sock}}
+ end},
+ #{desc => "bind dst",
+ cmd => fun(#{sock_dst := Sock, lsa_dst := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ?SEV_IPRINT("src bound"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("src bind failed: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "sockname dst socket",
+ cmd => fun(#{sock_dst := Sock} = State) ->
+ SADst = sock_sockname(Sock),
+ ?SEV_IPRINT("dst sockaddr: "
+ "~n ~p", [SADst]),
+ {ok, State#{sa_dst => SADst}}
+ end},
+ #{desc => "default recvttl for dst socket",
+ cmd => fun(#{sock_dst := Sock, get := Get} = _State) ->
+ case Get(Sock) of
+ {ok, false = Value} ->
+ ?SEV_IPRINT("dst recvttl: ~p", [Value]),
+ ok;
+ {ok, Unexpected} ->
+ ?SEV_EPRINT("Unexpected src recvttl: ~p",
+ [Unexpected]),
+ {error, {unexpected, Unexpected}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed getting (default) timestamp:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "send req (to dst) (wo explicit ttl)",
+ cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
+ Send(Sock, ?BASIC_REQ, Dst, default)
+ end},
+ #{desc => "recv req (from src)",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Src, [], ?BASIC_REQ}} ->
+ ok;
+ {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Recv Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Recv CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Recv Msg: ~p",
+ [Src, BadSrc,
+ [], BadCHdrs,
+ ?BASIC_REQ, BadReq]),
+ {error, {unexpected_data, UnexpData}};
+ {ok, UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Unexp Data: ~p",
+ [Src, [], ?BASIC_REQ, UnexpData]),
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+
+ #{desc => "send req (to dst) (w explicit ttl = 100)",
+ cmd => fun(#{sock_src := SSock,
+ sock_dst := DSock, sa_dst := Dst,
+ send := Send}) ->
+ case Send(SSock, ?BASIC_REQ, Dst, 100) of
+ ok ->
+ ok;
+ {error, einval = Reason} ->
+ %% IF we can't send it the test will not work
+ ?SEV_EPRINT("Cannot send TTL: "
+ "~p => SKIP", [Reason]),
+ (catch socket:close(SSock)),
+ (catch socket:close(DSock)),
+ {skip,
+ ?F("Cannot send with TTL: ~p", [Reason])};
+ {error, enoprotoopt = Reason} ->
+ %% On some platforms this is not
+ %% accepted (FreeBSD), so skip.
+ ?SEV_EPRINT("Expected Failure: "
+ "~p => SKIP", [Reason]),
+ (catch socket:close(SSock)),
+ (catch socket:close(DSock)),
+ {skip, Reason};
+ {error, _Reason} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "recv req (from src) - wo ttl",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Src, [], ?BASIC_REQ}} ->
+ ok;
+ {ok, {Src, [#{level := ip,
+ type := TTLType,
+ data := TTL}], ?BASIC_REQ}}
+ when ((TTLType =:= recvttl) andalso
+ (TTL =:= 255)) ->
+ %% This is the behaviopur on Solaris (11)
+ %% and maybe on other platforms...
+ ?SEV_IPRINT("Got (default) TTL (~w): ~p",
+ [TTLType, TTL]),
+ ok;
+ {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Recv Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Recv CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Recv Msg: ~p",
+ [Src, BadSrc,
+ [], BadCHdrs,
+ ?BASIC_REQ, BadReq]),
+ {error, {unexpected_data, UnexpData}};
+ {ok, UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Unexp Data: ~p",
+ [Src, [], ?BASIC_REQ, UnexpData]),
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+
+ #{desc => "enable recvttl on dst socket",
+ cmd => fun(#{sock_dst := Sock, set := Set} = _State) ->
+ case Set(Sock, true) of
+ ok ->
+ ?SEV_IPRINT("dst recvttl enabled"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed enabling recvttl:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "send req (to dst) (wo explicit ttl)",
+ cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
+ Send(Sock, ?BASIC_REQ, Dst, default)
+ end},
+ #{desc => "recv req (from src) - w default ttl",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Src, [#{level := ip,
+ type := TTLType,
+ data := TTL}], ?BASIC_REQ}}
+ when ((TTLType =:= ttl) orelse
+ (TTLType =:= recvttl)) ->
+ ?SEV_IPRINT("Got (default) TTL (~w): ~p",
+ [TTLType, TTL]),
+ ok;
+ {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Recv Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Recv CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Recv Msg: ~p",
+ [Src, BadSrc,
+ [#{level => ip,
+ type => ttl,
+ data => "something"}],
+ BadCHdrs,
+ ?BASIC_REQ, BadReq]),
+ {error, {unexpected_data, UnexpData}};
+ {ok, UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Unexp Data: ~p",
+ [Src, [#{level => ip,
+ type => ttl,
+ data => "something"}],
+ ?BASIC_REQ, UnexpData]),
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+
+ #{desc => "send req (to dst) (w explicit ttl = 100)",
+ cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
+ Send(Sock, ?BASIC_REQ, Dst, 100)
+ end},
+ #{desc => "recv req (from src) - w ttl = 100",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Src, [#{level := ip,
+ type := TTLType,
+ data := 100 = TTL}], ?BASIC_REQ}}
+ when ((TTLType =:= ttl) orelse
+ (TTLType =:= recvttl)) ->
+ ?SEV_IPRINT("Got TTL (~w): ~p",
+ [TTLType, TTL]),
+ ok;
+ {ok, {Src, [#{level := ip,
+ type := TTLType,
+ data := BadTTL}], ?BASIC_REQ}}
+ when ((TTLType =:= ttl) orelse
+ (TTLType =:= recvttl)) ->
+ ?SEV_EPRINT("Unexpected TTL: "
+ "~n Expect TTL: ~p"
+ "~n Recv TTL: ~p",
+ [100, BadTTL]),
+ {error, {unexpected_ttl, {100, BadTTL}}};
+ {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Recv Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Recv CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Recv Msg: ~p",
+ [Src, BadSrc,
+ [#{level => ip,
+ type => ttl,
+ data => 100}], BadCHdrs,
+ ?BASIC_REQ, BadReq]),
+ {error, {unexpected_data, UnexpData}};
+ {ok, UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Unexp Data: ~p",
+ [Src, [#{level => ip,
+ type => ttl,
+ data => 100}],
+ ?BASIC_REQ, UnexpData]),
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+
+ #{desc => "close src socket",
+ cmd => fun(#{sock_src := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock_src, State)}
+ end},
+ #{desc => "close dst socket",
+ cmd => fun(#{sock_dst := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock_dst, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+ Evaluator = ?SEV_START("tester", Seq, InitState),
+ ok = ?SEV_AWAIT_FINISH([Evaluator]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests the ip socket option 'tos' can be set and retrieved from a
+%% the socket its set on. It sets the type-of-server field in the IP
+%% header for a TCP or UDP socket.
+%% There is no way to fetch the value a received IP datagram.
+%% Default value is supposed to be '0'.
+%%
+
+api_opt_ip_tos_udp4(suite) ->
+ [];
+api_opt_ip_tos_udp4(doc) ->
+ [];
+api_opt_ip_tos_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_ip_tos_udp4,
+ fun() -> has_support_ip_tos() end,
+ fun() ->
+ Set = fun(Sock, Value) ->
+ socket:setopt(Sock, ip, tos, Value)
+ end,
+ Get = fun(Sock) ->
+ socket:getopt(Sock, ip, tos)
+ end,
+ InitState = #{set => Set,
+ get => Get},
+ ok = api_opt_ip_tos_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_opt_ip_tos_udp(InitState) ->
+ process_flag(trap_exit, true),
+ %% mincost is not supported on all platforms.
+ %% For instance, Solaris 10, does not have that constant.
+ %% Instead it has two others with, what appers to be,
+ %% completely different meanings...
+ %% So, avoid the complication by not using this value...
+ %% TOS1 = mincost, TOS1Str = atom_to_list(TOS1),
+ TOS2 = throughput, TOS2Str = atom_to_list(TOS2),
+ TOS3 = reliability, TOS3Str = atom_to_list(TOS3),
+ TOS4 = lowdelay, TOS4Str = atom_to_list(TOS4),
+ TOS5 = 42, TOS5Str = integer_to_list(TOS5),
+ Seq =
+ [
+ #{desc => "local address",
+ cmd => fun(State) ->
+ LSA = which_local_socket_addr(inet),
+ {ok, State#{lsa => LSA}}
+ end},
+
+ #{desc => "open socket",
+ cmd => fun(State) ->
+ Sock = sock_open(inet, dgram, udp),
+ {ok, State#{sock => Sock}}
+ end},
+ #{desc => "bind",
+ cmd => fun(#{sock := Sock, lsa := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ?SEV_IPRINT("socket bound"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("bind failed: ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "get default tos",
+ cmd => fun(#{sock := Sock, get := Get} = _State) ->
+ case Get(Sock) of
+ {ok, 0 = Value} ->
+ ?SEV_IPRINT("expected default tos: ~p", [Value]),
+ ok;
+ {ok, Unexpected} ->
+ ?SEV_EPRINT("Unexpected default tos: ~p",
+ [Unexpected]),
+ {error, {unexpected, Unexpected}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed getting (default) tos:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ %% #{desc => "set tos " ++ TOS1Str,
+ %% cmd => fun(#{sock := Sock, set := Set} = _State) ->
+ %% socket:setopt(Sock, otp, debug, true),
+ %% case Set(Sock, TOS1) of
+ %% ok ->
+ %% socket:setopt(Sock, otp, debug, false),
+ %% ?SEV_IPRINT("tos set to ~p", [TOS1]),
+ %% ok;
+ %% {error, Reason} = ERROR ->
+ %% socket:setopt(Sock, otp, debug, false),
+ %% ?SEV_EPRINT("Failed setting tos:"
+ %% " ~p", [Reason]),
+ %% ERROR
+ %% end
+ %% end},
+ %% #{desc => "get tos (expect " ++ TOS1Str ++ ")",
+ %% cmd => fun(#{sock := Sock, get := Get} = _State) ->
+ %% case Get(Sock) of
+ %% {ok, TOS1 = Value} ->
+ %% ?SEV_IPRINT("expected tos (~p)", [Value]),
+ %% ok;
+ %% {ok, Unexpected} ->
+ %% ?SEV_EPRINT("Unexpected tos: ~p",
+ %% [Unexpected]),
+ %% {error, {unexpected, Unexpected}};
+ %% {error, Reason} = ERROR ->
+ %% ?SEV_EPRINT("Failed getting (default) tos:"
+ %% " ~p", [Reason]),
+ %% ERROR
+ %% end
+ %% end},
+
+ #{desc => "set tos " ++ TOS2Str,
+ cmd => fun(#{sock := Sock, set := Set} = _State) ->
+ case Set(Sock, TOS2) of
+ ok ->
+ ?SEV_IPRINT("tos set to ~p", [TOS2]),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed setting tos:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "get tos (expect " ++ TOS2Str ++ ")",
+ cmd => fun(#{sock := Sock, get := Get} = _State) ->
+ case Get(Sock) of
+ {ok, TOS2 = Value} ->
+ ?SEV_IPRINT("expected tos (~p)", [Value]),
+ ok;
+ {ok, Unexpected} ->
+ ?SEV_EPRINT("Unexpected tos: ~p",
+ [Unexpected]),
+ {error, {unexpected, Unexpected}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed getting (default) tos:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "set tos " ++ TOS3Str,
+ cmd => fun(#{sock := Sock, set := Set} = _State) ->
+ case Set(Sock, TOS3) of
+ ok ->
+ ?SEV_IPRINT("tos set to ~p", [TOS3]),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed setting tos:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "get tos (expect " ++ TOS3Str ++ ")",
+ cmd => fun(#{sock := Sock, get := Get} = _State) ->
+ case Get(Sock) of
+ {ok, TOS3 = Value} ->
+ ?SEV_IPRINT("expected tos (~p)", [Value]),
+ ok;
+ {ok, Unexpected} ->
+ ?SEV_EPRINT("Unexpected tos: ~p",
+ [Unexpected]),
+ {error, {unexpected, Unexpected}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed getting (default) tos:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "set tos " ++ TOS4Str,
+ cmd => fun(#{sock := Sock, set := Set} = _State) ->
+ case Set(Sock, TOS4) of
+ ok ->
+ ?SEV_IPRINT("tos set to ~p", [TOS4]),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed setting tos:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "get tos (expect " ++ TOS4Str ++ ")",
+ cmd => fun(#{sock := Sock, get := Get} = _State) ->
+ case Get(Sock) of
+ {ok, TOS4 = Value} ->
+ ?SEV_IPRINT("expected tos (~p)", [Value]),
+ ok;
+ {ok, Unexpected} ->
+ ?SEV_EPRINT("Unexpected tos: ~p",
+ [Unexpected]),
+ {error, {unexpected, Unexpected}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed getting (default) tos:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "set tos " ++ TOS5Str,
+ cmd => fun(#{sock := Sock, set := Set} = _State) ->
+ case Set(Sock, TOS5) of
+ ok ->
+ ?SEV_IPRINT("tos set to ~p", [TOS5]),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed setting tos:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "get tos (expect " ++ TOS5Str ++ ")",
+ cmd => fun(#{sock := Sock, get := Get} = _State) ->
+ case Get(Sock) of
+ {ok, TOS5 = Value} ->
+ ?SEV_IPRINT("expected tos (~p)", [Value]),
+ ok;
+ {ok, Unexpected} ->
+ ?SEV_EPRINT("Unexpected tos: ~p",
+ [Unexpected]),
+ {error, {unexpected, Unexpected}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed getting (default) tos:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+ Evaluator = ?SEV_START("tester", Seq, InitState),
+ ok = ?SEV_AWAIT_FINISH([Evaluator]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests the ip socket option 'recverr' can be set and that the error
+%% queue can be read.
+%%
+
+api_opt_ip_recverr_udp4(suite) ->
+ [];
+api_opt_ip_recverr_udp4(doc) ->
+ [];
+api_opt_ip_recverr_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_ip_recverr_udp4,
+ fun() ->
+ has_support_ip_recverr()
+ end,
+ fun() ->
+ Set = fun(Sock, Key, Value) ->
+ socket:setopt(Sock, ip, Key, Value)
+ end,
+ Get = fun(Sock, Key) ->
+ socket:getopt(Sock, ip, Key)
+ end,
+ Send = fun(Sock, Data, Dest) ->
+ socket:sendto(Sock, Data, Dest, [], nowait)
+ end,
+ Recv = fun(Sock) ->
+ socket:recvfrom(Sock, 0, [], nowait)
+ end,
+ InitState = #{domain => inet,
+ proto => udp,
+ send => Send,
+ recv => Recv,
+ set => Set,
+ get => Get},
+ ok = api_opt_recverr_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests the ipv6 socket option 'recverr' can be set and that the error
+%% queue can be read.
+%%
+
+api_opt_ipv6_recverr_udp6(suite) ->
+ [];
+api_opt_ipv6_recverr_udp6(doc) ->
+ [];
+api_opt_ipv6_recverr_udp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_ipv6_recverr_udp6,
+ fun() ->
+ has_support_ipv6(),
+ has_support_ipv6_recverr()
+ end,
+ fun() ->
+ Set = fun(Sock, Key, Value) ->
+ socket:setopt(Sock, ipv6, Key, Value)
+ end,
+ Get = fun(Sock, Key) ->
+ socket:getopt(Sock, ipv6, Key)
+ end,
+ Send = fun(Sock, Data, Dest) ->
+ socket:sendto(Sock, Data, Dest, [], nowait)
+ end,
+ Recv = fun(Sock) ->
+ socket:recvfrom(Sock, 0, [], nowait)
+ end,
+ InitState = #{domain => inet6,
+ proto => udp,
+ send => Send,
+ recv => Recv,
+ set => Set,
+ get => Get},
+ ok = api_opt_recverr_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_opt_recverr_udp(InitState) ->
+ Seq =
+ [
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ ?SEV_IPRINT("test for ip:recvtos"),
+ case socket:open(Domain, dgram, udp) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind (to loopback)",
+ cmd => fun(#{sock := Sock} = _State) ->
+ case socket:bind(Sock, loopback) of
+ {ok, _} ->
+ ?SEV_IPRINT("bound"),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "enable recverr",
+ cmd => fun(#{sock := Sock, set := Set} = _State) ->
+ Set(Sock, recverr, true)
+ end},
+
+ #{desc => "disable mtu_discover",
+ cmd => fun(#{sock := Sock, set := Set} = _State) ->
+ Set(Sock, mtu_discover, dont)
+ end},
+
+ #{desc => "try (async) read (=> select)",
+ cmd => fun(#{sock := Sock, recv := Recv} = State) ->
+ case Recv(Sock) of
+ {select, SelectInfo} ->
+ ?SEV_IPRINT("expected select: "
+ "~n ~p", [SelectInfo]),
+ {ok, State#{rselect => SelectInfo}};
+ {ok, _} ->
+ ?SEV_EPRINT("unexpected successs"),
+ {error, unexpected_success};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("unexpected error: ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "try (dummy) send",
+ cmd => fun(#{domain := Domain, sock := Sock, send := Send} = State) ->
+ Dest = #{family => Domain,
+ addr => if
+ (Domain =:= inet) ->
+ {127,0,0,1};
+ (Domain =:= inet6) ->
+ {0,0,0,0,0,0,0,1}
+ end,
+ port => 1234},
+ case Send(Sock, <<"ping">>, Dest) of
+ ok ->
+ ?SEV_IPRINT("sent"),
+ ok;
+ {select, SelectInfo} ->
+ ?SEV_IPRINT("expected select: ~p",
+ [SelectInfo]),
+ {ok, State#{sselect => SelectInfo}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("unexpected error: ~p",
+ [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "await select message",
+ cmd => fun(#{sock := Sock,
+ rselect := {select_info, _, Ref}} = _State) ->
+ receive
+ {'$socket', Sock, select, Ref} ->
+ ?SEV_IPRINT("received expected (read) select message: "
+ "~n ~p", [Ref]),
+ ok
+ end
+ end},
+
+ #{desc => "try recv - expect econnrefused",
+ cmd => fun(#{sock := Sock, recv := Recv} = _State) ->
+ case Recv(Sock) of
+ {error, econnrefused = Reason} ->
+ ?SEV_IPRINT("expected failure: ~p", [Reason]),
+ ok;
+ {ok, _} ->
+ ?SEV_EPRINT("unexpected successs"),
+ {error, unexpected_success};
+ {select, SelectInfo} ->
+ ?SEV_EPRINT("unexpected select: ~p",
+ [SelectInfo]),
+ {error, unexpected_success};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("unexpected error: ~p",
+ [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "try recv error queue",
+ cmd => fun(#{domain := Domain, sock := Sock}) ->
+ %% Note that not all platforms that support
+ %% recverr, actually supports "encoding" the data
+ %% part, so we need to adjust for that.
+ Origin =
+ if (Domain =:= inet) -> icmp;
+ (Domain =:= inet6) -> icmp6
+ end,
+ Level =
+ if (Domain =:= inet) -> ip;
+ (Domain =:= inet6) -> ipv6
+ end,
+ case socket:recvmsg(Sock, [errqueue]) of
+ {ok, #{addr := #{family := Domain,
+ addr := Addr},
+ flags := [errqueue],
+ iov := [<<"ping">>],
+ ctrl := [#{level := Level,
+ type := recverr,
+ data :=
+ #{code := port_unreach,
+ data := 0,
+ error := econnrefused,
+ info := 0,
+ offender := #{family := Domain,
+ addr := Addr},
+ origin := Origin,
+ type := dest_unreach}
+ }]} = MsgHdr} ->
+ ?SEV_IPRINT("expected error queue (decoded): "
+ "~n ~p", [MsgHdr]),
+ ok;
+ {ok, #{addr := #{family := Domain,
+ addr := _Addr},
+ flags := [errqueue],
+ iov := [<<"ping">>],
+ ctrl := [#{level := Level,
+ type := recverr,
+ data := _Data}]} = _MsgHdr} ->
+ ?SEV_IPRINT("expected error queue"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("failed reading error queue: "
+ "~n ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+ Evaluator = ?SEV_START("tester", Seq, InitState),
+ ok = ?SEV_AWAIT_FINISH([Evaluator]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This intended to test "all" of the (currently) supported IPv4
+%% options that results in control message header(s).
+%% So, this is done on the receiving side:
+%%
+%% socket:setopt(Sock, ip, Flag, boolean()).
+%%
+%% For all subsequent *received* messages, a control message header
+%% for each of the enabled options will be received with the message.
+%%
+%% Only allowed for dgram and raw,
+%% although we only test this with dgram.
+%%
+%% Currently we *try* to use the following opts:
+%%
+%% pktinfo => pktinfo
+%% recvorigdstaddr => origdstaddr
+%% recvtos => tos
+%% recvttl => ttl
+%%
+%%
+%% Every time we add a test case for a new option (that results in
+%% a control message hedare), we should also add it here.
+%%
+%% Even though this is a IPv4 test case, we add the 'socket' timestamp
+%% option (just to fill up), but in the test to see if we should run
+%% the test (since its a IPv4 test case).
+%%
+
+api_opt_ip_mopts_udp4(suite) ->
+ [];
+api_opt_ip_mopts_udp4(doc) ->
+ [];
+api_opt_ip_mopts_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_ip_mopts_udp4,
+ fun() ->
+ case is_any_options_supported(
+ [{ip, pktinfo},
+ {ip, recvorigdstaddr},
+ {ip, recvtos},
+ {ip, recvttl}]) of
+ true ->
+ ok;
+ false ->
+ skip("None of the needed options are supported")
+ end
+ end,
+ fun() ->
+ %% If we get this far, we *know* that at least one of the
+ %% options are available.
+
+ %% This is list of all the options and there resulting
+ %% control message header type(s):
+ %% [{level,
+ %% 'ipv6 socket option',
+ %% 'control message header type',
+ %% default | value()}]
+ Opts =
+ case socket:is_supported(options, socket, timestamp) of
+ true ->
+ [{socket, timestamp, timestamp, default}];
+ false ->
+ []
+ end ++
+ case socket:is_supported(options, ip, pktinfo) of
+ true ->
+ [{ip, pktinfo, pktinfo, default}];
+ false ->
+ []
+ end ++
+ case socket:is_supported(options, ip, recvorigdstaddr) of
+ true ->
+ [{ip, recvorigdstaddr, origdstaddr, default}];
+ false ->
+ []
+ end ++
+ case socket:is_supported(options, ip, recvtos) of
+ true ->
+ %% It seems that sending any of the
+ %% TOS or TTL values will fail on:
+ %% FreeBSD
+ %% Linux when
+ %% version =< 3.12.60 (at least)
+ %% Don't know when this starts working,
+ %% but it works on:
+ %% Ubunto 16.04.6 => 4.15.0-65
+ %% SLES 12 SP2 => 4.4.120-92.70
+ %% so don't!
+ %%
+ %% The latest we know it not to work was a
+ %% SLES 12 (plain) at 3.12.50-52.54
+ %%
+ [{ip, recvtos, tos,
+ case os:type() of
+ {unix, freebsd} ->
+ default;
+ {unix, linux} ->
+ case os:version() of
+ Vsn when Vsn > {3,12,60} ->
+ 42;
+ _ ->
+ default
+ end;
+ _ ->
+ 42
+ end}];
+ false ->
+ []
+ end ++
+ case os:type() of
+ {unix, darwin} ->
+ [];
+ _ ->
+ case socket:is_supported(options, ip, recvttl) of
+ true ->
+ %% It seems that sending any of the
+ %% TOS or TTL values will fail on:
+ %% FreeBSD and NetBSD
+ %% Linux when
+ %% version =< 3.12.60 (at least)
+ %% so don't!
+ %% See recvtos above for more info.
+ [{ip, recvttl, ttl,
+ case os:type() of
+ {unix, BSD}
+ when (BSD =:= freebsd) orelse
+ (BSD =:= netbsd) ->
+ default;
+ {unix, netbsd} ->
+ default;
+ {unix, linux} ->
+ case os:version() of
+ Vsn when Vsn > {3,12,60} ->
+ 42;
+ _ ->
+ default
+ end;
+ _ ->
+ 42
+ end}];
+ false ->
+ []
+ end
+ end,
+
+ Enable = fun(Sock, Level, Opt) ->
+ ?SEV_IPRINT("try enable [~w] ~p", [Level, Opt]),
+ socket:setopt(Sock, Level, Opt, true)
+ end,
+ Send = fun(Sock, Data, Dest, []) ->
+ MsgHdr = #{addr => Dest,
+ iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr);
+ (Sock, Data, Dest, Hdrs) when is_list(Hdrs) ->
+ CMsgHdrs = [#{level => Level,
+ type => Type,
+ data => Val} ||
+ {Level, Type, Val} <- Hdrs],
+ MsgHdr = #{addr => Dest,
+ ctrl => CMsgHdrs,
+ iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock) of
+ {ok, #{addr := Source,
+ ctrl := CMsgHdrs,
+ iov := [Data]}} ->
+ {ok, {Source, CMsgHdrs, Data}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet,
+ proto => udp,
+ opts => Opts,
+ send => Send,
+ recv => Recv,
+ enable => Enable},
+ ok = api_opt_ip_mopts_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_opt_ip_mopts_udp(InitState) ->
+ Seq =
+ [
+ #{desc => "local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa_src => LSA,
+ lsa_dst => LSA}}
+ end},
+
+ #{desc => "open src socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ Sock = sock_open(Domain, dgram, Proto),
+ {ok, State#{sock_src => Sock}}
+ end},
+ #{desc => "bind src",
+ cmd => fun(#{sock_src := Sock, lsa_src := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ?SEV_IPRINT("src bound"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("src bind failed: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "sockname src socket",
+ cmd => fun(#{sock_src := Sock} = State) ->
+ SASrc = sock_sockname(Sock),
+ ?SEV_IPRINT("src sockaddr: "
+ "~n ~p", [SASrc]),
+ {ok, State#{sa_src => SASrc}}
+ end},
+
+ #{desc => "open dst socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ Sock = sock_open(Domain, dgram, Proto),
+ {ok, State#{sock_dst => Sock}}
+ end},
+ #{desc => "bind dst",
+ cmd => fun(#{sock_dst := Sock, lsa_dst := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ?SEV_IPRINT("src bound"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("src bind failed: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "sockname dst socket",
+ cmd => fun(#{sock_dst := Sock} = State) ->
+ SADst = sock_sockname(Sock),
+ ?SEV_IPRINT("dst sockaddr: "
+ "~n ~p", [SADst]),
+ {ok, State#{sa_dst => SADst}}
+ end},
+
+ #{desc => "enable options on dst socket",
+ cmd => fun(#{sock_dst := DSock,
+ sock_src := SSock,
+ opts := Opts,
+ enable := Enable} = _State) ->
+ %% If we fail to enable *any* of the options,
+ %% we give up.
+ E = fun({Level, Opt, _, _}) ->
+ case Enable(DSock, Level, Opt) of
+ ok ->
+ ?SEV_IPRINT("dst [~w] ~w enabled",
+ [Level, Opt]),
+ ok;
+ {error, enoprotoopt = Reason} ->
+ ?SEV_EPRINT("Expected "
+ "Failure: "
+ "~p => SKIP",
+ [Reason]),
+ (catch socket:close(DSock)),
+ (catch socket:close(SSock)),
+ {skip, Reason};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed "
+ "setting ~w:"
+ " ~p",
+ [Opt, Reason]),
+ throw(ERROR)
+ end
+ end,
+ lists:foreach(E, Opts),
+ ok
+ end},
+
+ #{desc => "send req (to dst)",
+ cmd => fun(#{sock_src := Sock,
+ sa_dst := Dst,
+ opts := Opts,
+ send := Send}) ->
+ Hdrs = [{Level, Type, Data} ||
+ {Level, _, Type, Data} <-
+ Opts, (Data =/= default)],
+ Send(Sock, ?BASIC_REQ, Dst, Hdrs)
+ end},
+ #{desc => "recv req (from src)",
+ cmd => fun(#{sock_dst := Sock,
+ sa_src := Src,
+ recv := Recv,
+ opts := Opts}) ->
+ case Recv(Sock) of
+ {ok, {Src, CMsgHdrs, ?BASIC_REQ}}
+ when length(CMsgHdrs) =:= length(Opts) ->
+ ?SEV_IPRINT("Got (expected) cmsg headers: "
+ "~n ~p", [CMsgHdrs]),
+ %% We should really verify the headers:
+ %% values, types and so on...
+ ok;
+ {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Recv Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Recv CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Recv Msg: ~p",
+ [Src, BadSrc,
+ [{Level, Type} ||
+ {Level, _, Type, _} <- Opts],
+ BadCHdrs,
+ ?BASIC_REQ, BadReq]),
+ {error, {unexpected_data, UnexpData}};
+ {ok, UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Unexp Data: ~p",
+ [Src,
+ [{Level, Type} ||
+ {Level, _, Type, _} <- Opts],
+ ?BASIC_REQ,
+ UnexpData]),
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+
+ #{desc => "close src socket",
+ cmd => fun(#{sock_src := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock_src, State)}
+ end},
+ #{desc => "close dst socket",
+ cmd => fun(#{sock_dst := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock_dst, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+ Evaluator = ?SEV_START("tester", Seq, InitState),
+ ok = ?SEV_AWAIT_FINISH([Evaluator]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests that the IPv6 pktinfo control message header is received on
+%% incoming datagrams (UDP and RAW) when setting the socket 'ipv6'
+%% option recvpktinfo is set to true when using.
+%% So, this is done on the receiving side:
+%%
+%% socket:setopt(Sock, ipv6, recvpktinfo, boolean()).
+%%
+%% For all subsequent *received* messages, the pktinfo control message
+%% header will be with the message.
+%%
+%% Only allowed for dgram and raw,
+%% although we only test this with dgram.
+%%
+
+api_opt_ipv6_recvpktinfo_udp6(suite) ->
+ [];
+api_opt_ipv6_recvpktinfo_udp6(doc) ->
+ [];
+api_opt_ipv6_recvpktinfo_udp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_ipv6_recvpktinfo_udp6,
+ fun() ->
+ has_support_ipv6(),
+ has_support_ipv6_recvpktinfo()
+ end,
+ fun() ->
+ Set = fun(Sock, Value) ->
+ socket:setopt(Sock, ipv6, recvpktinfo, Value)
+ end,
+ Get = fun(Sock) ->
+ socket:getopt(Sock, ipv6, recvpktinfo)
+ end,
+ Send = fun(Sock, Data, Dest, default) ->
+ MsgHdr = #{addr => Dest,
+ iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr);
+ (Sock, Data, Dest, Info) ->
+ %% We do not support this at the moment!!!
+ CMsgHdr = #{level => ipv6,
+ type => pktinfo,
+ data => Info},
+ MsgHdr = #{addr => Dest,
+ ctrl => [CMsgHdr],
+ iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock) of
+ {ok, #{addr := Source,
+ ctrl := CMsgHdrs,
+ iov := [Data]}} ->
+ {ok, {Source, CMsgHdrs, Data}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet6,
+ proto => udp,
+ send => Send,
+ recv => Recv,
+ set => Set,
+ get => Get},
+ ok = api_opt_ipv6_recvpktinfo_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_opt_ipv6_recvpktinfo_udp(InitState) ->
+ Seq =
+ [
+ #{desc => "local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa_src => LSA,
+ lsa_dst => LSA}}
+ end},
+
+ #{desc => "open src socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ Sock = sock_open(Domain, dgram, Proto),
+ {ok, State#{sock_src => Sock}}
+ end},
+ #{desc => "bind src",
+ cmd => fun(#{sock_src := Sock, lsa_src := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ?SEV_IPRINT("src bound"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("src bind failed: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "sockname src socket",
+ cmd => fun(#{sock_src := Sock} = State) ->
+ SASrc = sock_sockname(Sock),
+ ?SEV_IPRINT("src sockaddr: "
+ "~n ~p", [SASrc]),
+ {ok, State#{sa_src => SASrc}}
+ end},
+
+ #{desc => "open dst socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ Sock = sock_open(Domain, dgram, Proto),
+ {ok, State#{sock_dst => Sock}}
+ end},
+ #{desc => "bind dst",
+ cmd => fun(#{sock_dst := Sock, lsa_dst := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ?SEV_IPRINT("src bound"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("src bind failed: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "sockname dst socket",
+ cmd => fun(#{sock_dst := Sock} = State) ->
+ SADst = sock_sockname(Sock),
+ ?SEV_IPRINT("dst sockaddr: "
+ "~n ~p", [SADst]),
+ {ok, State#{sa_dst => SADst}}
+ end},
+ #{desc => "default pktinfo for dst socket",
+ cmd => fun(#{sock_dst := Sock, get := Get} = _State) ->
+ case Get(Sock) of
+ {ok, false = Value} ->
+ ?SEV_IPRINT("dst recvttl: ~p", [Value]),
+ ok;
+ {ok, Unexpected} ->
+ ?SEV_EPRINT("Unexpected src recvtos: ~p",
+ [Unexpected]),
+ {error, {unexpected, Unexpected}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed getting (default) recvtos:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "send req (to dst) (wo (explicit) pktinfo)",
+ cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
+ Send(Sock, ?BASIC_REQ, Dst, default)
+ end},
+ #{desc => "recv req (from src)",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Src, [], ?BASIC_REQ}} ->
+ ok;
+ {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Recv Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Recv CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Recv Msg: ~p",
+ [Src, BadSrc,
+ [], BadCHdrs,
+ ?BASIC_REQ, BadReq]),
+ {error, {unexpected_data, UnexpData}};
+ {ok, UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Unexp Data: ~p",
+ [Src, [], ?BASIC_REQ, UnexpData]),
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+
+ #{desc => "enable pktinfo on dst socket",
+ cmd => fun(#{sock_dst := Sock, set := Set} = _State) ->
+ case Set(Sock, true) of
+ ok ->
+ ?SEV_IPRINT("dst pktinfo enabled"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed setting pktinfo:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "send req (to dst) (wo explicit pktinfo)",
+ cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
+ Send(Sock, ?BASIC_REQ, Dst, default)
+ end},
+ #{desc => "recv req (from src) - w default pktinfo",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Src, [#{level := ipv6,
+ type := pktinfo,
+ data := #{addr := Addr,
+ ifindex := IfIdx}}],
+ ?BASIC_REQ}} ->
+ ?SEV_IPRINT("Got (default) Pkt Info: "
+ "~n Addr: ~p"
+ "~n If Index: ~p",
+ [Addr, IfIdx]),
+ ok;
+ {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Recv Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Recv CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Recv Msg: ~p",
+ [Src, BadSrc,
+ #{level => ipv6,
+ type => pktinfo,
+ data => "something"} , BadCHdrs,
+ ?BASIC_REQ, BadReq]),
+ {error, {unexpected_data, UnexpData}};
+ {ok, UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Unexp Data: ~p",
+ [Src, [], ?BASIC_REQ, UnexpData]),
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+
+
+ #{desc => "close src socket",
+ cmd => fun(#{sock_src := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock_src, State)}
+ end},
+ #{desc => "close dst socket",
+ cmd => fun(#{sock_dst := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock_dst, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+ Evaluator = ?SEV_START("tester", Seq, InitState),
+ ok = ?SEV_AWAIT_FINISH([Evaluator]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests that the 'flow info' control message header is received when
+%% setting the socket 'ipv6' option flowinfo is set to true when using
+%% sendmsg/recvmsg on an IPv6 UDP (dgram) socket.
+%% So, this is done on the receiving side:
+%%
+%% socket:setopt(Sock, ipv6, flowinfo, boolean()).
+%%
+%% For all subsequent *received* messages, the 'flow info' control message
+%% header will be with the message.
+%%
+%% Only allowed for dgram and raw,
+%% although we only test this with dgram.
+%%
+%% There seem to be some weirdness with the definition of this
+%% option, so its defined in an include file we don't include
+%% (directly or indirectly). And since some of the defines
+%% are occure in a file we *do* include (via netinet/in.h), we
+%% leave it as is for now...
+%%
+
+api_opt_ipv6_flowinfo_udp6(suite) ->
+ [];
+api_opt_ipv6_flowinfo_udp6(doc) ->
+ [];
+api_opt_ipv6_flowinfo_udp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_ipv6_flowinfo_udp6,
+ fun() ->
+ has_support_ipv6(),
+ has_support_ipv6_flowinfo()
+ end,
+ fun() ->
+ Set = fun(Sock, Value) ->
+ socket:setopt(Sock, ipv6, flowinfo, Value)
+ end,
+ Get = fun(Sock) ->
+ socket:getopt(Sock, ipv6, flowinfo)
+ end,
+ Send = fun(Sock, Data, Dest) ->
+ MsgHdr = #{addr => Dest,
+ iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock) of
+ {ok, #{addr := Source,
+ ctrl := CMsgHdrs,
+ iov := [Data]}} ->
+ {ok, {Source, CMsgHdrs, Data}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet6,
+ proto => udp,
+ send => Send,
+ recv => Recv,
+ set => Set,
+ get => Get},
+ ok = api_opt_ipv6_flowinfo_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_opt_ipv6_flowinfo_udp(InitState) ->
+ Seq =
+ [
+ #{desc => "local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa_src => LSA,
+ lsa_dst => LSA}}
+ end},
+
+ #{desc => "open src socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ Sock = sock_open(Domain, dgram, Proto),
+ {ok, State#{sock_src => Sock}}
+ end},
+ #{desc => "bind src",
+ cmd => fun(#{sock_src := Sock, lsa_src := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ?SEV_IPRINT("src bound"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("src bind failed: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "sockname src socket",
+ cmd => fun(#{sock_src := Sock} = State) ->
+ SASrc = sock_sockname(Sock),
+ ?SEV_IPRINT("src sockaddr: "
+ "~n ~p", [SASrc]),
+ {ok, State#{sa_src => SASrc}}
+ end},
+
+ #{desc => "open dst socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ Sock = sock_open(Domain, dgram, Proto),
+ {ok, State#{sock_dst => Sock}}
+ end},
+ #{desc => "bind dst",
+ cmd => fun(#{sock_dst := Sock, lsa_dst := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ?SEV_IPRINT("src bound"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("src bind failed: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "sockname dst socket",
+ cmd => fun(#{sock_dst := Sock} = State) ->
+ SADst = sock_sockname(Sock),
+ ?SEV_IPRINT("dst sockaddr: "
+ "~n ~p", [SADst]),
+ {ok, State#{sa_dst => SADst}}
+ end},
+ #{desc => "default flowinfo for dst socket",
+ cmd => fun(#{sock_dst := Sock, get := Get} = _State) ->
+ case Get(Sock) of
+ {ok, false = Value} ->
+ ?SEV_IPRINT("dst flowinfo: ~p", [Value]),
+ ok;
+ {ok, Unexpected} ->
+ ?SEV_EPRINT("Unexpected src flowinfo: ~p",
+ [Unexpected]),
+ {error, {unexpected, Unexpected}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed getting (default) flowinfo:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "send req (to dst)",
+ cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
+ Send(Sock, ?BASIC_REQ, Dst)
+ end},
+ #{desc => "recv req (from src)",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Src, [], ?BASIC_REQ}} ->
+ ok;
+ {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Recv Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Recv CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Recv Msg: ~p",
+ [Src, BadSrc,
+ [], BadCHdrs,
+ ?BASIC_REQ, BadReq]),
+ {error, {unexpected_data, UnexpData}};
+ {ok, UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Unexp Data: ~p",
+ [Src, [], ?BASIC_REQ, UnexpData]),
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+
+ #{desc => "enable flowinfo on dst socket",
+ cmd => fun(#{sock_dst := Sock, set := Set} = _State) ->
+ case Set(Sock, true) of
+ ok ->
+ ?SEV_IPRINT("dst flowinfo enabled"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed setting flowinfo:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "send req (to dst)",
+ cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
+ Send(Sock, ?BASIC_REQ, Dst)
+ end},
+ #{desc => "recv req (from src)",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Src, [#{level := ipv6,
+ type := flowinfo,
+ data := FlowID}], ?BASIC_REQ}} ->
+ ?SEV_IPRINT("Got flow info: "
+ "~n Flow ID: ~p", [FlowID]),
+ ok;
+ {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Recv Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Recv CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Recv Msg: ~p",
+ [Src, BadSrc,
+ #{level => ipv6,
+ type => flowinfo,
+ data => "something"},
+ BadCHdrs,
+ ?BASIC_REQ, BadReq]),
+ {error, {unexpected_data, UnexpData}};
+ {ok, UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Unexp Data: ~p",
+ [Src, [], ?BASIC_REQ, UnexpData]),
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+
+ #{desc => "close src socket",
+ cmd => fun(#{sock_src := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock_src, State)}
+ end},
+ #{desc => "close dst socket",
+ cmd => fun(#{sock_dst := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock_dst, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+ Evaluator = ?SEV_START("tester", Seq, InitState),
+ ok = ?SEV_AWAIT_FINISH([Evaluator]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests that the 'hop limit' control message header is received when
+%% setting the socket 'ipv6' hoplimit or recvhoplimit option is set to
+%% true when using sendmsg/recvmsg on an IPv6 UDP (dgram) socket.
+%% So, this is done on the receiving side:
+%%
+%% socket:setopt(Sock, ipv6, recvhoplimit | hoplimit, boolean()).
+%%
+%% For all subsequent *received* messages, the 'hop limit' control message
+%% header will be with the message.
+%% We make the assumption, that if 'recvhoplimit' is supported, then
+%% that option is used to order the hoplimit control message, otherwise
+%% hoplimit is used.
+%%
+%% Only allowed for dgram and raw,
+%% although we only test this with dgram.
+%%
+%% <Note>
+%%
+%% There is also an IPV6_RECVHOPLIMIT option defined in the header
+%% file (bits/in.h) with a different value. This is not mentioned
+%% in the man page. Deprecated? More testing needed...
+%%
+%% </Note>
+%%
+
+api_opt_ipv6_hoplimit_udp6(suite) ->
+ [];
+api_opt_ipv6_hoplimit_udp6(doc) ->
+ [];
+api_opt_ipv6_hoplimit_udp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_ipv6_hoplimit_udp6,
+ fun() ->
+ has_support_ipv6(),
+ has_support_ipv6_hoplimit_or_recvhoplimit(),
+ is_good_enough_darwin({9,8,0})
+ end,
+ fun() ->
+ %% Begin by choosing which of the options we shall use
+ Opt = case socket:is_supported(options, ipv6, recvhoplimit) of
+ true -> recvhoplimit;
+ false -> hoplimit
+ end,
+ Set = fun(Sock, Value) ->
+ ?SEV_IPRINT("try set ~p: ~p", [Opt, Value]),
+ socket:setopt(Sock, ipv6, Opt, Value)
+ end,
+ Get = fun(Sock) ->
+ ?SEV_IPRINT("try get ~p", [Opt]),
+ socket:getopt(Sock, ipv6, Opt)
+ end,
+ Send = fun(Sock, Data, Dest) ->
+ MsgHdr = #{addr => Dest,
+ iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock) of
+ {ok, #{addr := Source,
+ ctrl := CMsgHdrs,
+ iov := [Data]}} ->
+ {ok, {Source, CMsgHdrs, Data}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet6,
+ proto => udp,
+ send => Send,
+ recv => Recv,
+ set => Set,
+ get => Get},
+ ok = api_opt_ipv6_hoplimit_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_opt_ipv6_hoplimit_udp(InitState) ->
+ Seq =
+ [
+ #{desc => "local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa_src => LSA,
+ lsa_dst => LSA}}
+ end},
+
+ #{desc => "open src socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ Sock = sock_open(Domain, dgram, Proto),
+ {ok, State#{sock_src => Sock}}
+ end},
+ #{desc => "bind src",
+ cmd => fun(#{sock_src := Sock, lsa_src := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ?SEV_IPRINT("src bound"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("src bind failed: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "sockname src socket",
+ cmd => fun(#{sock_src := Sock} = State) ->
+ SASrc = sock_sockname(Sock),
+ ?SEV_IPRINT("src sockaddr: "
+ "~n ~p", [SASrc]),
+ {ok, State#{sa_src => SASrc}}
+ end},
+
+ #{desc => "open dst socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ Sock = sock_open(Domain, dgram, Proto),
+ {ok, State#{sock_dst => Sock}}
+ end},
+ #{desc => "bind dst",
+ cmd => fun(#{sock_dst := Sock, lsa_dst := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ?SEV_IPRINT("src bound"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("src bind failed: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "sockname dst socket",
+ cmd => fun(#{sock_dst := Sock} = State) ->
+ SADst = sock_sockname(Sock),
+ ?SEV_IPRINT("dst sockaddr: "
+ "~n ~p", [SADst]),
+ {ok, State#{sa_dst => SADst}}
+ end},
+ #{desc => "default [recv]hoplimit for dst socket",
+ cmd => fun(#{sock_dst := Sock, get := Get} = State) ->
+ case Get(Sock) of
+ {ok, false = Value} ->
+ ?SEV_IPRINT("dst [recv]hoplimit: ~p",
+ [Value]),
+ ok;
+ {ok, Unexpected} ->
+ ?SEV_EPRINT("Unexpected src [recv]hoplimit: ~p",
+ [Unexpected]),
+ {error, {unexpected, Unexpected}};
+ {error, enoprotoopt = Reason} ->
+ %% On some platforms this is not accepted
+ %% for UDP, so skip this part (UDP).
+ ?SEV_EPRINT("Expected Failure: "
+ "~p => SKIP", [Reason]),
+ (catch socket:close(Sock)),
+ (catch socket:close(maps:get_value(sock_src,
+ State))),
+ {skip, Reason};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed getting (default) hoplimit:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "send req (to dst)",
+ cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
+ Send(Sock, ?BASIC_REQ, Dst)
+ end},
+ #{desc => "recv req (from src)",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Src, [], ?BASIC_REQ}} ->
+ ok;
+ {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Recv Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Recv CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Recv Msg: ~p",
+ [Src, BadSrc,
+ [], BadCHdrs,
+ ?BASIC_REQ, BadReq]),
+ {error, {unexpected_data, UnexpData}};
+ {ok, UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Unexp Data: ~p",
+ [Src, [], ?BASIC_REQ, UnexpData]),
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+
+ #{desc => "enable [recv]hoplimit on dst socket",
+ cmd => fun(#{sock_dst := Sock, set := Set} = State) ->
+ case Set(Sock, true) of
+ ok ->
+ ?SEV_IPRINT("dst [recv]hoplimit enabled"),
+ ok;
+ {error, enoprotoopt = Reason} ->
+ %% On some platforms this is not accepted
+ %% for UDP, so skip this part (UDP).
+ ?SEV_EPRINT("Expected Failure: "
+ "~p => SKIP", [Reason]),
+ (catch socket:close(Sock)),
+ (catch socket:close(maps:get_value(sock_src,
+ State))),
+ {skip, Reason};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed setting hoplimit:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "send req (to dst) (wo explicit ttl)",
+ cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
+ Send(Sock, ?BASIC_REQ, Dst)
+ end},
+ #{desc => "recv req (from src)",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Src, [#{level := ipv6,
+ type := hoplimit,
+ data := HL}], ?BASIC_REQ}}
+ when is_integer(HL) ->
+ ?SEV_IPRINT("Got hop limit: "
+ "~n Hop Limit: ~p", [HL]),
+ ok;
+ {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Recv Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Recv CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Recv Msg: ~p",
+ [Src, BadSrc,
+ #{level => ipv6,
+ type => hoplimit,
+ data => "something"},
+ BadCHdrs,
+ ?BASIC_REQ, BadReq]),
+ {error, {unexpected_data, UnexpData}};
+ {ok, UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Unexp Data: ~p",
+ [Src, #{level => ipv6,
+ type => hoplimit,
+ data => "something"},
+ ?BASIC_REQ, UnexpData]),
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+
+ #{desc => "close src socket",
+ cmd => fun(#{sock_src := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock_src, State)}
+ end},
+ #{desc => "close dst socket",
+ cmd => fun(#{sock_dst := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock_dst, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+ Evaluator = ?SEV_START("tester", Seq, InitState),
+ ok = ?SEV_AWAIT_FINISH([Evaluator]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests that the 'tclass' control message header is received when
+%% setting the socket 'ipv6' tclass or recvtclass option is set to
+%% true when using sendmsg/recvmsg on an IPv6 UDP (dgram) socket.
+%% So, this is done on the receiving side:
+%%
+%% socket:setopt(Sock, ipv6, recvtclass | tclass, boolean()).
+%%
+%% For all subsequent *received* messages, the 'tclass' control message
+%% header will be with the message.
+%% We make the assumption, that if 'recvtclass' is supported, then
+%% that option is used to order the tclass control message, otherwise
+%% tclass is used.
+%%
+%% Only allowed for dgram and raw,
+%% although we only test this with dgram.
+%%
+%% <Note>
+%%
+%% There is also an IPV6_RECVTCLASS option defined in the header
+%% file (bits/in.h) with a different value. This is not mentioned
+%% in the man page. Deprecated? More testing needed...
+%%
+%% </Note>
+%%
+
+api_opt_ipv6_tclass_udp6(suite) ->
+ [];
+api_opt_ipv6_tclass_udp6(doc) ->
+ [];
+api_opt_ipv6_tclass_udp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_ipv6_tclass_udp6,
+ fun() ->
+ has_support_ipv6(),
+ has_support_ipv6_tclass_or_recvtclass()
+ end,
+ fun() ->
+ %% Begin by choosing which of the options we shall use
+ Opt = case socket:is_supported(options, ipv6, recvtclass) of
+ true -> recvtclass;
+ false -> tclass
+ end,
+ Set = fun(Sock, Value) ->
+ ?SEV_IPRINT("try set ~p: ~p", [Opt, Value]),
+ socket:setopt(Sock, ipv6, Opt, Value)
+ end,
+ Get = fun(Sock) ->
+ ?SEV_IPRINT("try get ~p", [Opt]),
+ socket:getopt(Sock, ipv6, Opt)
+ end,
+ Send = fun(Sock, Data, Dest, default) ->
+ MsgHdr = #{addr => Dest,
+ iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr);
+ (Sock, Data, Dest, TC) ->
+ TCHdr = #{level => ipv6,
+ type => tclass,
+ data => TC},
+ CMsgHdrs = [TCHdr],
+ MsgHdr = #{addr => Dest,
+ ctrl => CMsgHdrs,
+ iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock) of
+ {ok, #{addr := Source,
+ ctrl := CMsgHdrs,
+ iov := [Data]}} ->
+ {ok, {Source, CMsgHdrs, Data}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet6,
+ proto => udp,
+ send => Send,
+ recv => Recv,
+ set => Set,
+ get => Get},
+ ok = api_opt_ipv6_tclass_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_opt_ipv6_tclass_udp(InitState) ->
+ Seq =
+ [
+ #{desc => "local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa_src => LSA,
+ lsa_dst => LSA}}
+ end},
+
+ #{desc => "open src socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ Sock = sock_open(Domain, dgram, Proto),
+ {ok, State#{sock_src => Sock}}
+ end},
+ #{desc => "bind src",
+ cmd => fun(#{sock_src := Sock, lsa_src := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ?SEV_IPRINT("src bound"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("src bind failed: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "sockname src socket",
+ cmd => fun(#{sock_src := Sock} = State) ->
+ SASrc = sock_sockname(Sock),
+ ?SEV_IPRINT("src sockaddr: "
+ "~n ~p", [SASrc]),
+ {ok, State#{sa_src => SASrc}}
+ end},
+
+ #{desc => "open dst socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ Sock = sock_open(Domain, dgram, Proto),
+ {ok, State#{sock_dst => Sock}}
+ end},
+ #{desc => "bind dst",
+ cmd => fun(#{sock_dst := Sock, lsa_dst := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ?SEV_IPRINT("src bound"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("src bind failed: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "sockname dst socket",
+ cmd => fun(#{sock_dst := Sock} = State) ->
+ SADst = sock_sockname(Sock),
+ ?SEV_IPRINT("dst sockaddr: "
+ "~n ~p", [SADst]),
+ {ok, State#{sa_dst => SADst}}
+ end},
+ #{desc => "default [recv]tclass for dst socket",
+ cmd => fun(#{sock_dst := Sock, get := Get} = State) ->
+ case Get(Sock) of
+ {ok, false = Value} ->
+ ?SEV_IPRINT("dst [recv]tclass: ~p",
+ [Value]),
+ ok;
+ {ok, Unexpected} ->
+ ?SEV_EPRINT("Unexpected src [recv]tclass: ~p",
+ [Unexpected]),
+ {error, {unexpected, Unexpected}};
+ {error, enoprotoopt = Reason} ->
+ %% On some platforms this is not accepted
+ %% for UDP, so skip this part (UDP).
+ ?SEV_EPRINT("Expected Failure: "
+ "~p => SKIP", [Reason]),
+ (catch socket:close(Sock)),
+ (catch socket:close(maps:get_value(sock_src,
+ State))),
+ {skip, Reason};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed getting (default) tclass:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "send req (to dst)",
+ cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
+ Send(Sock, ?BASIC_REQ, Dst, default)
+ end},
+ #{desc => "recv req (from src)",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Src, [], ?BASIC_REQ}} ->
+ ok;
+ {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Recv Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Recv CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Recv Msg: ~p",
+ [Src, BadSrc,
+ [], BadCHdrs,
+ ?BASIC_REQ, BadReq]),
+ {error, {unexpected_data, UnexpData}};
+ {ok, UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Unexp Data: ~p",
+ [Src, [], ?BASIC_REQ, UnexpData]),
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+
+ #{desc => "enable [recv]tclass on dst socket",
+ cmd => fun(#{sock_dst := Sock, set := Set} = State) ->
+ case Set(Sock, true) of
+ ok ->
+ ?SEV_IPRINT("dst [recv]tclass enabled"),
+ ok;
+ {error, enoprotoopt = Reason} ->
+ %% On some platforms this is not accepted
+ %% for UDP, so skip this part (UDP).
+ ?SEV_EPRINT("Expected Failure: "
+ "~p => SKIP", [Reason]),
+ (catch socket:close(Sock)),
+ (catch socket:close(maps:get_value(sock_src,
+ State))),
+ {skip, Reason};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed setting tclass:"
+ " ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ #{desc => "send req (to dst) (wo explicit tc)",
+ cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
+ Send(Sock, ?BASIC_REQ, Dst, default)
+ end},
+ #{desc => "recv req (from src)",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Src, [#{level := ipv6,
+ type := tclass,
+ data := TClass}], ?BASIC_REQ}}
+ when is_integer(TClass) ->
+ ?SEV_IPRINT("Got tclass: "
+ "~n TClass: ~p", [TClass]),
+ ok;
+ {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Recv Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Recv CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Recv Msg: ~p",
+ [Src, BadSrc,
+ #{level => ipv6,
+ type => tclass,
+ data => "something"},
+ BadCHdrs,
+ ?BASIC_REQ, BadReq]),
+ {error, {unexpected_data, UnexpData}};
+ {ok, UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Unexp Data: ~p",
+ [Src, #{level => ipv6,
+ type => tclass,
+ data => "something"},
+ ?BASIC_REQ, UnexpData]),
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+
+ #{desc => "send req (to dst) (w explicit tc = 1)",
+ cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
+ Send(Sock, ?BASIC_REQ, Dst, 1)
+ end},
+ #{desc => "recv req (from src)",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, {Src, [#{level := ipv6,
+ type := tclass,
+ data := 1 = TClass}], ?BASIC_REQ}}
+ when is_integer(TClass) ->
+ ?SEV_IPRINT("Got (expected) tclass: "
+ "~n TClass: ~p", [TClass]),
+ ok;
+ {ok, {_Src, [#{level := ipv6,
+ type := tclass,
+ data := TClass}], ?BASIC_REQ}}
+ when is_integer(TClass) ->
+ ?SEV_EPRINT("Unexpected tclass: "
+ "~n Expect TClass: ~p"
+ "~n Recv TClass: ~p",
+ [1, TClass]),
+ {error, {unexpected_tclass, TClass}};
+ {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Recv Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Recv CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Recv Msg: ~p",
+ [Src, BadSrc,
+ #{level => ipv6,
+ type => tclass,
+ data => "something"},
+ BadCHdrs,
+ ?BASIC_REQ, BadReq]),
+ {error, {unexpected_data, UnexpData}};
+ {ok, UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Unexp Data: ~p",
+ [Src, #{level => ipv6,
+ type => tclass,
+ data => "something"},
+ ?BASIC_REQ, UnexpData]),
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+
+ #{desc => "close src socket",
+ cmd => fun(#{sock_src := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock_src, State)}
+ end},
+ #{desc => "close dst socket",
+ cmd => fun(#{sock_dst := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock_dst, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+ Evaluator = ?SEV_START("tester", Seq, InitState),
+ ok = ?SEV_AWAIT_FINISH([Evaluator]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This intended to test "all" of the (currently) supported IPv6
+%% options that results in control message header(s).
+%% So, this is done on the receiving side:
+%%
+%% socket:setopt(Sock, ipv6, Flag, boolean()).
+%%
+%% For all subsequent *received* messages, a control message header
+%% for each of the enabled options will be received with the message.
+%%
+%% Only allowed for dgram and raw,
+%% although we only test this with dgram.
+%%
+%% Currently we *try* to use the following opts:
+%%
+%% recvpktinfo | pktinfo => pktinfo
+%% flowinfo => flowinfo
+%% recvhoplimit | hoplimit => hoplimit
+%% recvtclass | tclass => tclass
+%%
+%%
+%% Every time we add a test case for a new option (that results in
+%% a control message hedare), we should also add it here.
+%%
+%% Even though this is a IPv6 test case, we add the 'socket' timestamp
+%% option (just to fill up), but in the test to see if we should run
+%% the test (since its a IPv6 test case).
+%%
+
+api_opt_ipv6_mopts_udp6(suite) ->
+ [];
+api_opt_ipv6_mopts_udp6(doc) ->
+ [];
+api_opt_ipv6_mopts_udp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_ipv6_mopts_udp6,
+ fun() ->
+ has_support_ipv6(),
+ case is_any_options_supported(
+ [{ipv6, recvpktinfo},
+ {ipv6, flowinfo},
+ {ipv6, recvhoplimit},
+ {ipv6, hoplimit},
+ {ipv6, recvtclass},
+ {ipv6, tclass}]) of
+ true ->
+ ok;
+ false ->
+ skip("None of the needed options are supported")
+ end,
+ %% The problem here is hoplimit on darwin 9.8.0,
+ %% but I can't be bothered to adjust the test case,
+ %% just skip on that machine (there is only one)...
+ is_good_enough_darwin({9,8,0})
+ end,
+ fun() ->
+ %% If we get this far, we *know* that at least one of the
+ %% options are available.
+
+ %% This is list of all the options and there resulting
+ %% control message header type(s):
+ %% [{'ipv6 socket option', 'control message header type'}]
+ Opts =
+ case socket:is_supported(options, socket, timestamp) of
+ true ->
+ [{socket, timestamp, timestamp, default}];
+ false ->
+ []
+ end ++
+ case socket:is_supported(options, ipv6, recvpktinfo) of
+ true ->
+ [{ipv6, recvpktinfo, pktinfo, default}];
+ false ->
+ []
+ end ++
+ case socket:is_supported(options, ipv6, flowinfo) of
+ true ->
+ [{ipv6, flowinfo, flowinfo, default}];
+ false ->
+ []
+ end ++
+ case socket:is_supported(options, ipv6, recvhoplimit) of
+ true ->
+ [{ipv6, recvhoplimit, hoplimit, default}];
+ false ->
+ case socket:is_supported(options, ipv6, hoplimit) of
+ true ->
+ [{ipv6, hoplimit, hoplimit, default}];
+ false ->
+ []
+ end
+ end ++
+ case socket:is_supported(options, ipv6, recvtclass) of
+ true ->
+ [{ipv6, recvtclass, tclass, 42}];
+ false ->
+ case socket:is_supported(options, ipv6, tclass) of
+ true ->
+ [{ipv6, tclass, tclass, 42}];
+ false ->
+ []
+ end
+ end,
+
+ Enable = fun(Sock, Level, Opt) ->
+ ?SEV_IPRINT("try enable [~w] ~p", [Level, Opt]),
+ socket:setopt(Sock, Level, Opt, true)
+ end,
+ Send = fun(Sock, Data, Dest, []) ->
+ MsgHdr = #{addr => Dest,
+ iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr);
+ (Sock, Data, Dest, Hdrs) when is_list(Hdrs) ->
+ CMsgHdrs = [#{level => Level,
+ type => Type,
+ data => Val} ||
+ {Level, Type, Val} <- Hdrs],
+ MsgHdr = #{addr => Dest,
+ ctrl => CMsgHdrs,
+ iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock) of
+ {ok, #{addr := Source,
+ ctrl := CMsgHdrs,
+ iov := [Data]}} ->
+ {ok, {Source, CMsgHdrs, Data}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet6,
+ proto => udp,
+ opts => Opts,
+ send => Send,
+ recv => Recv,
+ enable => Enable},
+ ok = api_opt_ipv6_mopts_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_opt_ipv6_mopts_udp(InitState) ->
+ Seq =
+ [
+ #{desc => "local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa_src => LSA,
+ lsa_dst => LSA}}
+ end},
+
+ #{desc => "open src socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ Sock = sock_open(Domain, dgram, Proto),
+ {ok, State#{sock_src => Sock}}
+ end},
+ #{desc => "bind src",
+ cmd => fun(#{sock_src := Sock, lsa_src := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ?SEV_IPRINT("src bound"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("src bind failed: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "sockname src socket",
+ cmd => fun(#{sock_src := Sock} = State) ->
+ SASrc = sock_sockname(Sock),
+ ?SEV_IPRINT("src sockaddr: "
+ "~n ~p", [SASrc]),
+ {ok, State#{sa_src => SASrc}}
+ end},
+
+ #{desc => "open dst socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ Sock = sock_open(Domain, dgram, Proto),
+ {ok, State#{sock_dst => Sock}}
+ end},
+ #{desc => "bind dst",
+ cmd => fun(#{sock_dst := Sock, lsa_dst := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ?SEV_IPRINT("src bound"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("src bind failed: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "sockname dst socket",
+ cmd => fun(#{sock_dst := Sock} = State) ->
+ SADst = sock_sockname(Sock),
+ ?SEV_IPRINT("dst sockaddr: "
+ "~n ~p", [SADst]),
+ {ok, State#{sa_dst => SADst}}
+ end},
+
+ #{desc => "enable options on dst socket",
+ cmd => fun(#{sock_dst := DSock,
+ sock_src := SSock,
+ opts := Opts,
+ enable := Enable} = _State) ->
+ %% If we fail to enable *any* of the options,
+ %% we give up.
+ E = fun({Level, Opt, _, _}) ->
+ case Enable(DSock, Level, Opt) of
+ ok ->
+ ?SEV_IPRINT("dst [~w] ~w enabled",
+ [Level, Opt]),
+ ok;
+ {error, enoprotoopt = Reason} ->
+ ?SEV_EPRINT("Expected "
+ "Failure: "
+ "~p => SKIP",
+ [Reason]),
+ (catch socket:close(DSock)),
+ (catch socket:close(SSock)),
+ {skip, Reason};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed "
+ "setting ~w:"
+ " ~p",
+ [Opt, Reason]),
+ throw(ERROR)
+ end
+ end,
+ lists:foreach(E, Opts),
+ ok
+ end},
+
+ #{desc => "send req (to dst)",
+ cmd => fun(#{sock_src := Sock,
+ sa_dst := Dst,
+ opts := Opts,
+ send := Send}) ->
+ Hdrs = [{Level, Type, Data} ||
+ {Level, _, Type, Data} <-
+ Opts, (Data =/= default)],
+ Send(Sock, ?BASIC_REQ, Dst, Hdrs)
+ end},
+ #{desc => "recv req (from src)",
+ cmd => fun(#{sock_dst := Sock,
+ sa_src := Src,
+ recv := Recv,
+ opts := Opts}) ->
+ case Recv(Sock) of
+ {ok, {Src, CMsgHdrs, ?BASIC_REQ}}
+ when length(CMsgHdrs) =:= length(Opts) ->
+ ?SEV_IPRINT("Got (expected) cmsg headers: "
+ "~n ~p", [CMsgHdrs]),
+ %% We should really verify the headers:
+ %% values, types and so on...
+ ok;
+ {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Recv Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Recv CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Recv Msg: ~p",
+ [Src, BadSrc,
+ [{Level, Type} ||
+ {Level, _, Type, _} <- Opts],
+ BadCHdrs,
+ ?BASIC_REQ, BadReq]),
+ {error, {unexpected_data, UnexpData}};
+ {ok, UnexpData} ->
+ ?SEV_EPRINT("Unexpected msg: "
+ "~n Expect Source: ~p"
+ "~n Expect CHdrs: ~p"
+ "~n Expect Msg: ~p"
+ "~n Unexp Data: ~p",
+ [Src,
+ [{Level, Type} ||
+ {Level, _, Type, _} <- Opts],
+ ?BASIC_REQ,
+ UnexpData]),
+ {error, {unexpected_data, UnexpData}};
+ {error, _} = ERROR ->
+ %% At the moment there is no way to get
+ %% status or state for the socket...
+ ERROR
+ end
+ end},
+
+ #{desc => "close src socket",
+ cmd => fun(#{sock_src := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock_src, State)}
+ end},
+ #{desc => "close dst socket",
+ cmd => fun(#{sock_dst := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock_dst, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+ Evaluator = ?SEV_START("tester", Seq, InitState),
+ ok = ?SEV_AWAIT_FINISH([Evaluator]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests that the congestion tcp socket option.
+%%
+%% According to the man page (on linux) for this option it *should* be
+%% possible to both get and set *allowed* algorithms. But when we attempt
+%% to set, we get 'enoent'.
+%% Accoring to /proc/sys/net/ipv4/tcp_allowed_congestion_control that
+%% allgorithm was allowed, so...
+%% For now, we only test that we can get (it could be a bug in our code)
+
+api_opt_tcp_congestion_tcp4(suite) ->
+ [];
+api_opt_tcp_congestion_tcp4(doc) ->
+ [];
+api_opt_tcp_congestion_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_tcp_congestion_tcp4,
+ fun() -> has_support_tcp_congestion() end,
+ fun() ->
+ Set = fun(Sock, Value) when is_list(Value) ->
+ socket:setopt(Sock, tcp, congestion, Value)
+ end,
+ Get = fun(Sock) ->
+ socket:getopt(Sock, tcp, congestion)
+ end,
+ InitState = #{domain => inet,
+ proto => tcp,
+ set => Set,
+ get => Get},
+ ok = api_opt_tcp_congestion_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_opt_tcp_congestion_tcp(InitState) ->
+ process_flag(trap_exit, true),
+ ServerSeq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa => LSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ case socket:open(Domain, stream, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{sock := LSock, lsa := LSA} = _State) ->
+ case sock_bind(LSock, LSA) of
+ {ok, Port} ->
+ ?SEV_IPRINT("bound to port: ~w", [Port]),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+
+ %% The actual test
+ #{desc => "await continue (get congestion)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, get_congestion)
+ end},
+ #{desc => "get congestion",
+ cmd => fun(#{sock := Sock, get := Get} = State) ->
+ case Get(Sock) of
+ {ok, Algorithm} ->
+ ?SEV_IPRINT("algorithm: ~s", [Algorithm]),
+ {ok, State#{alg => Algorithm}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (get congestion)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, get_congestion),
+ ok
+ end},
+
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close connection socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, server, init),
+ ok
+ end},
+
+ %% *** The actual test ***
+ #{desc => "order server to continue (with get-congestion)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, get_congestion),
+ ok
+ end},
+ #{desc => "await server ready (get-congestion)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, get_congestion)
+ end},
+
+
+ %% *** Termination ***
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Server),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Server} = State) ->
+ ?SEV_AWAIT_TERMINATION(Server),
+ State1 = maps:remove(server, State),
+ {ok, State1}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ i("start server evaluator"),
+ Server = ?SEV_START("server", ServerSeq, InitState),
+
+ i("start tester evaluator"),
+ TesterInitState = #{server => Server#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ ok = ?SEV_AWAIT_FINISH([Server, Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests that the cork tcp socket option.
+%%
+%% This is a very simple test. We simple set and get the value.
+%% To test that it has an effect is just "to much work"...
+%%
+%% Reading the man page it seems like (on linux) that the
+%% value resets itself after some (short) time...
+
+api_opt_tcp_cork_tcp4(suite) ->
+ [];
+api_opt_tcp_cork_tcp4(doc) ->
+ [];
+api_opt_tcp_cork_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_tcp_cork_tcp4,
+ fun() -> has_support_tcp_cork() end,
+ fun() ->
+ Set = fun(Sock, Value) when is_boolean(Value) ->
+ socket:setopt(Sock, tcp, cork, Value)
+ end,
+ Get = fun(Sock) ->
+ socket:getopt(Sock, tcp, cork)
+ end,
+ InitState = #{domain => inet,
+ proto => tcp,
+ set => Set,
+ get => Get},
+ ok = api_opt_tcp_cork_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_opt_tcp_cork_tcp(InitState) ->
+ process_flag(trap_exit, true),
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa => LSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ case socket:open(Domain, stream, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{sock := LSock, lsa := LSA} = _State) ->
+ case sock_bind(LSock, LSA) of
+ {ok, Port} ->
+ ?SEV_IPRINT("bound to port: ~w", [Port]),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ %% The actual test
+ #{desc => "get (default) cork (= false)",
+ cmd => fun(#{sock := Sock, get := Get} = _State) ->
+ case Get(Sock) of
+ {ok, false = Value} ->
+ ?SEV_IPRINT("cork default: ~p", [Value]),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "enable cork (=> true)",
+ cmd => fun(#{sock := Sock, set := Set} = _State) ->
+ case Set(Sock, true) of
+ ok ->
+ ?SEV_IPRINT("cork enabled"),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "get cork (= true)",
+ cmd => fun(#{sock := Sock, get := Get} = _State) ->
+ case Get(Sock) of
+ {ok, true = Value} ->
+ ?SEV_IPRINT("cork: ~p", [Value]),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ %% *** Termination ***
+ #{desc => "close connection socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start tester evaluator"),
+ Tester = ?SEV_START("tester", TesterSeq, InitState),
+
+ ok = ?SEV_AWAIT_FINISH([Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests that the maxseg tcp socket option.
+%%
+%% This is a very simple test. We simple set and get the value.
+%% To test that it has an effect is just "to much work"...
+%%
+%% Note that there is no point in reading this value back,
+%% since the kernel imposes its own rules with regard
+%% to what is an acceptible value.
+%%
+
+api_opt_tcp_maxseg_tcp4(suite) ->
+ [];
+api_opt_tcp_maxseg_tcp4(doc) ->
+ [];
+api_opt_tcp_maxseg_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_tcp_maxseg_tcp4,
+ fun() -> has_support_tcp_maxseg() end,
+ fun() ->
+ Set = fun(Sock, Value) when is_integer(Value) ->
+ socket:setopt(Sock, tcp, maxseg, Value)
+ end,
+ Get = fun(Sock) ->
+ socket:getopt(Sock, tcp, maxseg)
+ end,
+ InitState = #{domain => inet,
+ proto => tcp,
+ set => Set,
+ get => Get},
+ ok = api_opt_tcp_maxseg_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_opt_tcp_maxseg_tcp(InitState) ->
+ process_flag(trap_exit, true),
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa => LSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ case socket:open(Domain, stream, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{sock := LSock, lsa := LSA} = _State) ->
+ case sock_bind(LSock, LSA) of
+ {ok, Port} ->
+ ?SEV_IPRINT("bound to port: ~w", [Port]),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ %% The actual test
+ #{desc => "get (default) maxseg",
+ cmd => fun(#{sock := Sock, get := Get} = State) ->
+ case Get(Sock) of
+ {ok, DefMaxSeg} ->
+ ?SEV_IPRINT("maxseg default: ~p", [DefMaxSeg]),
+ {ok, State#{def_maxseg => DefMaxSeg}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% Note that there is no point in reading this value back,
+ %% since the kernel imposes its own rules with regard
+ %% to what is an acceptible value.
+ %% So, even if the set operation is a success, the value
+ %% still might not have changed.
+ %%
+ %% Note that not all platforms allow this to be set!
+ %% Since this is the *last* operation in the test sequence
+ %% (before termination) we also accept error reason = einval
+ %% as success (rather then skip).
+ %% The same goes for the error reason = enoprotoopt (Solaris).
+ #{desc => "(maybe) change maxseg (default + 16)",
+ cmd => fun(#{sock := Sock,
+ set := Set,
+ def_maxseg := DefMaxSeg} = _State) ->
+ NewMaxSeg = DefMaxSeg + 16,
+ case Set(Sock, NewMaxSeg) of
+ ok ->
+ ?SEV_IPRINT("maxseg (maybe) changed (to ~w)",
+ [NewMaxSeg]),
+ ok;
+ {error, Reason} when (Reason =:= einval) orelse
+ (Reason =:= enoprotoopt) ->
+ ?SEV_IPRINT("change not allowed (~w)",
+ [Reason]),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** Termination ***
+ #{desc => "close connection socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start tester evaluator"),
+ Tester = ?SEV_START("tester", TesterSeq, InitState),
+
+ ok = ?SEV_AWAIT_FINISH([Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests that the nodelay tcp socket option.
+%%
+%% This is a very simple test. We simple set and get the value.
+%% To test that it has an effect is just "to much work"...
+
+api_opt_tcp_nodelay_tcp4(suite) ->
+ [];
+api_opt_tcp_nodelay_tcp4(doc) ->
+ [];
+api_opt_tcp_nodelay_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_tcp_nodelay_tcp4,
+ fun() -> has_support_tcp_nodelay() end,
+ fun() ->
+ Set = fun(Sock, Value) when is_boolean(Value) ->
+ socket:setopt(Sock, tcp, nodelay, Value)
+ end,
+ Get = fun(Sock) ->
+ socket:getopt(Sock, tcp, nodelay)
+ end,
+ InitState = #{domain => inet,
+ proto => tcp,
+ set => Set,
+ get => Get},
+ ok = api_opt_tcp_nodelay_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_opt_tcp_nodelay_tcp(InitState) ->
+ process_flag(trap_exit, true),
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa => LSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ case socket:open(Domain, stream, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{sock := Sock, lsa := LSA} = _State) ->
+ case sock_bind(Sock, LSA) of
+ {ok, Port} ->
+ ?SEV_IPRINT("bound to port: ~w", [Port]),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ %% The actual test
+ #{desc => "get (default) nodelay (= false)",
+ cmd => fun(#{sock := Sock, get := Get} = _State) ->
+ case Get(Sock) of
+ {ok, false = Value} ->
+ ?SEV_IPRINT("nodelay default: ~p", [Value]),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "enable nodelay (=> true)",
+ cmd => fun(#{sock := Sock, set := Set} = _State) ->
+ case Set(Sock, true) of
+ ok ->
+ ?SEV_IPRINT("nodelay enabled"),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "get nodelay (= true)",
+ cmd => fun(#{sock := Sock, get := Get} = _State) ->
+ case Get(Sock) of
+ {ok, true = Value} ->
+ ?SEV_IPRINT("nodelay: ~p", [Value]),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ %% *** Termination ***
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start tester evaluator"),
+ Tester = ?SEV_START("tester", TesterSeq, InitState),
+
+ ok = ?SEV_AWAIT_FINISH([Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests that the cork udp socket option.
+%%
+%% This is a very simple test. We simple set and get the value.
+%% To test that it has an effect is just "to much work"...
+%%
+
+api_opt_udp_cork_udp4(suite) ->
+ [];
+api_opt_udp_cork_udp4(doc) ->
+ [];
+api_opt_udp_cork_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_udp_cork_udp4,
+ fun() -> has_support_udp_cork() end,
+ fun() ->
+ Set = fun(Sock, Value) when is_boolean(Value) ->
+ socket:setopt(Sock, udp, cork, Value)
+ end,
+ Get = fun(Sock) ->
+ socket:getopt(Sock, udp, cork)
+ end,
+ InitState = #{domain => inet,
+ proto => udp,
+ set => Set,
+ get => Get},
+ ok = api_opt_udp_cork_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_opt_udp_cork_udp(InitState) ->
+ process_flag(trap_exit, true),
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa => LSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ case socket:open(Domain, dgram, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{sock := Sock, lsa := LSA} = _State) ->
+ case sock_bind(Sock, LSA) of
+ {ok, Port} ->
+ ?SEV_IPRINT("bound to port: ~w", [Port]),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ %% The actual test
+ #{desc => "get (default) cork (= false)",
+ cmd => fun(#{sock := Sock, get := Get} = _State) ->
+ case Get(Sock) of
+ {ok, false = Value} ->
+ ?SEV_IPRINT("cork default: ~p", [Value]),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "enable cork (=> true)",
+ cmd => fun(#{sock := Sock, set := Set} = _State) ->
+ case Set(Sock, true) of
+ ok ->
+ ?SEV_IPRINT("cork enabled"),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "get cork (= true)",
+ cmd => fun(#{sock := Sock, get := Get} = _State) ->
+ case Get(Sock) of
+ {ok, true = Value} ->
+ ?SEV_IPRINT("cork: ~p", [Value]),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ %% *** Termination ***
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start tester evaluator"),
+ Tester = ?SEV_START("tester", TesterSeq, InitState),
+
+ ok = ?SEV_AWAIT_FINISH([Tester]).
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% %%
+%% API OPERATIONS WITH TIMEOUT %%
+%% %%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the connect timeout option
+%% on an IPv4 TCP (stream) socket.
+api_to_connect_tcp4(suite) ->
+ [];
+api_to_connect_tcp4(doc) ->
+ [];
+api_to_connect_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ Cond = fun() -> api_to_connect_cond() end,
+ tc_try(api_to_connect_tcp4,
+ Cond,
+ fun() ->
+ InitState = #{domain => inet,
+ backlog => 1,
+ timeout => 5000,
+ connect_limit => 3},
+ ok = api_to_connect_tcp(InitState)
+ end).
+
+api_to_connect_cond() ->
+ api_to_connect_cond(os:type(), os:version()).
+
+%% I don't know exactly at which version this starts to work.
+%% I know it does not work for 4.4.*, but is does for 4.15.
+%% So, just to simplify, we require atleast 4.15
+api_to_connect_cond({unix, linux}, {Maj, Min, _Rev}) ->
+ if
+ (Maj > 4) ->
+ ok;
+ ((Maj =:= 4) andalso (Min >= 15)) ->
+ ok;
+ true ->
+ skip("TC does not work")
+ end;
+%% Only test on one machine, which has version 6.3, and there it does
+%% not work, so disable for all.
+api_to_connect_cond({unix, openbsd}, _) ->
+ skip("TC does not work");
+api_to_connect_cond({unix, freebsd}, {Maj, Min, _Rev}) ->
+ if
+ ((Maj >= 10) andalso (Min >= 4)) ->
+ ok;
+ true ->
+ skip("TC may not work")
+ end;
+api_to_connect_cond({unix, sunos}, {Maj, Min, _Rev}) ->
+ if
+ ((Maj >= 5) andalso (Min >= 10)) ->
+ ok;
+ true ->
+ skip("TC may not work")
+ end;
+api_to_connect_cond(_, _) ->
+ skip("TC may not work").
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the connect timeout option
+%% on an IPv6 TCP (stream) socket.
+api_to_connect_tcp6(suite) ->
+ [];
+api_to_connect_tcp6(doc) ->
+ [];
+api_to_connect_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_to_connect_tcp6,
+ fun() -> has_support_ipv6(), api_to_connect_cond() end,
+ fun() ->
+ InitState = #{domain => inet6,
+ backlog => 1,
+ timeout => 5000,
+ connect_limit => 3},
+ ok = api_to_connect_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% We use the backlog (listen) argument to test this.
+%% Note that the behaviour of the TCP "server side" can vary when
+%% a client connect to a "busy" server (full backlog).
+%% For instance, on FreeBSD (11.2) the reponse when the backlog is full
+%% is a econreset.
+
+api_to_connect_tcp(InitState) ->
+ process_flag(trap_exit, true),
+
+ ServerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ {Tester, Backlog} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ backlog => Backlog}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "create listen socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{lsock := LSock, local_sa := LSA} = State) ->
+ case sock_bind(LSock, LSA) of
+ {ok, Port} ->
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket (with backlog = 1)",
+ cmd => fun(#{lsock := LSock, backlog := Backlog}) ->
+ socket:listen(LSock, Backlog)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, lport := Port}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Port),
+ ok
+ end},
+
+ %% Termination
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{lsock := Sock} = State) ->
+ sock_close(Sock),
+ State1 = maps:remove(lport, State),
+ State2 = maps:remove(sock, State1),
+ {ok, State2}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ ClientSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ {Tester, ServerSA} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ server_sa => ServerSA}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "create node",
+ cmd => fun(#{host := Host} = State) ->
+ ?SEV_IPRINT("try create node on ~p", [Host]),
+ case start_node(Host, client) of
+ {ok, Node} ->
+ ?SEV_IPRINT("client node ~p started",
+ [Node]),
+ {ok, State#{node => Node}};
+ {error, Reason} ->
+ {skip, Reason}
+ end
+ end},
+ #{desc => "monitor client node",
+ cmd => fun(#{node := Node} = _State) ->
+ true = erlang:monitor_node(Node, true),
+ ok
+ end},
+ #{desc => "start remote client on client node",
+ cmd => fun(#{node := Node} = State) ->
+ Pid = api_toc_tcp_client_start(Node),
+ ?SEV_IPRINT("remote client ~p started", [Pid]),
+ {ok, State#{rclient => Pid}}
+ end},
+ #{desc => "monitor remote client",
+ cmd => fun(#{rclient := Pid}) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order remote client to start",
+ cmd => fun(#{rclient := Client,
+ server_sa := ServerSA}) ->
+ ?SEV_ANNOUNCE_START(Client, ServerSA),
+ ok
+ end},
+ #{desc => "await remote client ready",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, rclient, init,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (connect)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = State) ->
+ case ?SEV_AWAIT_CONTINUE(Tester, tester, connect,
+ [{rclient, Client}]) of
+ {ok, {ConTimeout, ConLimit}} ->
+ {ok, State#{connect_timeout => ConTimeout,
+ connect_limit => ConLimit}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order remote client to continue (connect)",
+ cmd => fun(#{rclient := RClient,
+ connect_timeout := ConTimeout,
+ connect_limit := ConLimit}) ->
+ ?SEV_ANNOUNCE_CONTINUE(RClient, connect,
+ {ConTimeout, ConLimit}),
+ ok
+ end},
+ #{desc => "await remote client ready (connect)",
+ cmd => fun(#{tester := Tester,
+ rclient := RClient} = State) ->
+ case ?SEV_AWAIT_READY(RClient, rclient, connect,
+ [{tester, Tester}]) of
+ {ok, ok = _Result} ->
+ {ok, maps:remove(connect_limit, State)};
+ {ok, {error, {connect_limit_reached,R,L}}} ->
+ {skip,
+ ?LIB:f("Connect limit reached ~w: ~w",
+ [L, R])};
+ {ok, Result} ->
+ Result;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "announce ready (connect)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, connect),
+ ok
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester,
+ rclient := RClient} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester,
+ [{rclient, RClient}]) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "kill remote client",
+ cmd => fun(#{rclient := Client}) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await remote client termination",
+ cmd => fun(#{rclient := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(rclient, State),
+ {ok, State1}
+ end},
+ #{desc => "stop client node",
+ cmd => fun(#{node := Node} = _State) ->
+ stop_node(Node)
+ end},
+ #{desc => "await client node termination",
+ cmd => fun(#{node := Node} = State) ->
+ receive
+ {nodedown, Node} ->
+ State1 = maps:remove(node_id, State),
+ State2 = maps:remove(node, State1),
+ {ok, State2}
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Server} = _State) ->
+ _MRef = erlang:monitor(process, Server),
+ ok
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Client} = _State) ->
+ _MRef = erlang:monitor(process, Client),
+ ok
+ end},
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "order server start",
+ cmd => fun(#{server := Server,
+ backlog := Backlog}) ->
+ ?SEV_ANNOUNCE_START(Server, Backlog),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Server, local_sa := LSA} = State) ->
+ {ok, Port} = ?SEV_AWAIT_READY(Server, server, init),
+ ServerSA = LSA#{port => Port},
+ {ok, State#{server_sa => ServerSA}}
+ end},
+ #{desc => "order client start",
+ cmd => fun(#{client := Client,
+ server_sa := ServerSA}) ->
+ ?SEV_ANNOUNCE_START(Client, ServerSA),
+ ok
+ end},
+ #{desc => "await client ready (init)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, init),
+ ok
+ end},
+
+ %% The actual test
+ %% The server does nothing (this is the point), no accept,
+ %% the client tries to connect.
+ #{desc => "order client continue (connect)",
+ cmd => fun(#{client := Client,
+ timeout := Timeout,
+ connect_limit := ConLimit} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, connect,
+ {Timeout, ConLimit}),
+ ok
+ end},
+ #{desc => "await client ready (connect)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, client, connect,
+ [{server, Server}]) of
+ ok ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ %% *** Terminate server ***
+ #{desc => "order client terminate",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await client down",
+ cmd => fun(#{client := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(client, State),
+ {ok, State1}
+ end},
+ #{desc => "order server terminate",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Server),
+ ok
+ end},
+ #{desc => "await server down",
+ cmd => fun(#{server := Server} = State) ->
+ ?SEV_AWAIT_TERMINATION(Server),
+ State1 = maps:remove(server, State),
+ State2 = maps:remove(server_sa, State1),
+ {ok, State2}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("create server evaluator"),
+ ServerInitState = #{domain => maps:get(domain, InitState)},
+ Server = ?SEV_START("server", ServerSeq, ServerInitState),
+
+ i("create client evaluator"),
+ ClientInitState = #{host => local_host(),
+ domain => maps:get(domain, InitState)},
+ Client = ?SEV_START("client", ClientSeq, ClientInitState),
+
+ i("create tester evaluator"),
+ TesterInitState = InitState#{server => Server#ev.pid,
+ client => Client#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator(s)"),
+ ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]).
+
+
+api_toc_tcp_client_start(Node) ->
+ Self = self(),
+ Fun = fun() -> api_toc_tcp_client(Self) end,
+ erlang:spawn(Node, Fun).
+
+api_toc_tcp_client(Parent) ->
+ api_toc_tcp_client_init(Parent),
+ ServerSA = api_toc_tcp_client_await_start(Parent),
+ Domain = maps:get(family, ServerSA),
+ api_toc_tcp_client_announce_ready(Parent, init),
+ {To, ConLimit} = api_toc_tcp_client_await_continue(Parent, connect),
+ Result = api_to_connect_tcp_await_timeout(To, ServerSA, Domain, ConLimit),
+ ?SEV_IPRINT("result: ~p", [Result]),
+ api_toc_tcp_client_announce_ready(Parent, connect, Result),
+ Reason = api_toc_tcp_client_await_terminate(Parent),
+ exit(Reason).
+
+api_toc_tcp_client_init(Parent) ->
+ put(sname, "rclient"),
+ %% i("api_toc_tcp_client_init -> entry"),
+ _MRef = erlang:monitor(process, Parent),
+ ok.
+
+api_toc_tcp_client_await_start(Parent) ->
+ %% i("api_toc_tcp_client_await_start -> entry"),
+ ?SEV_AWAIT_START(Parent).
+
+api_toc_tcp_client_announce_ready(Parent, Slogan) ->
+ ?SEV_ANNOUNCE_READY(Parent, Slogan).
+api_toc_tcp_client_announce_ready(Parent, Slogan, Result) ->
+ ?SEV_ANNOUNCE_READY(Parent, Slogan, Result).
+
+api_toc_tcp_client_await_continue(Parent, Slogan) ->
+ %% i("api_toc_tcp_client_await_continue -> entry"),
+ case ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan) of
+ ok ->
+ ok;
+ {ok, Extra} ->
+ Extra;
+ {error, Reason} ->
+ exit({await_continue, Slogan, Reason})
+ end.
+
+api_toc_tcp_client_await_terminate(Parent) ->
+ %% i("api_toc_tcp_client_await_terminate -> entry"),
+ case ?SEV_AWAIT_TERMINATE(Parent, parent) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ Reason
+ end.
+
+api_to_connect_tcp_await_timeout(To, ServerSA, Domain, ConLimit) ->
+ LSA = which_local_socket_addr(Domain),
+ NewSock = fun() ->
+ S = case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ Sock;
+ {error, OReason} ->
+ ?FAIL({open, OReason})
+ end,
+ case socket:bind(S, LSA) of
+ {ok, _} ->
+ S;
+ {error, BReason} ->
+ ?FAIL({bind, BReason})
+ end
+ end,
+ api_to_connect_tcp_await_timeout(1, ConLimit, To, ServerSA, NewSock, []).
+
+api_to_connect_tcp_await_timeout(ID, ConLimit, _To, _ServerSA, _NewSock, Acc)
+ when (ID > ConLimit) ->
+ api_to_connect_tcp_await_timeout3(Acc),
+ {error, {connect_limit_reached, ID, ConLimit}};
+api_to_connect_tcp_await_timeout(ID, ConLimit, To, ServerSA, NewSock, Acc) ->
+ case api_to_connect_tcp_await_timeout2(ID, To, ServerSA, NewSock) of
+ ok ->
+ %% ?SEV_IPRINT("success when number of socks: ~w", [length(Acc)]),
+ api_to_connect_tcp_await_timeout3(Acc),
+ ok;
+ {ok, Sock} ->
+ %% ?SEV_IPRINT("~w: unexpected success (connect)", [ID]),
+ api_to_connect_tcp_await_timeout(ID+1, ConLimit,
+ To, ServerSA, NewSock,
+ [Sock|Acc]);
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+api_to_connect_tcp_await_timeout2(_ID, To, ServerSA, NewSock) ->
+ Sock = NewSock(),
+ %% ?SEV_IPRINT("~w: try connect", [ID]),
+ Start = t(),
+ case socket:connect(Sock, ServerSA, To) of
+ {error, timeout} ->
+ Stop = t(),
+ TDiff = Stop - Start,
+ if
+ (TDiff >= To) ->
+ (catch socket:close(Sock)),
+ ok;
+ true ->
+ (catch socket:close(Sock)),
+ ?FAIL({unexpected_timeout, TDiff, To})
+ end;
+ {error, econnreset = _Reason} ->
+ (catch socket:close(Sock)),
+ ok;
+ {error, Reason} ->
+ (catch socket:close(Sock)),
+ ?FAIL({connect, Reason});
+ ok ->
+ {ok, Sock}
+ end.
+
+api_to_connect_tcp_await_timeout3([]) ->
+ ok;
+api_to_connect_tcp_await_timeout3([Sock|Socka]) ->
+ (catch socket:close(Sock)),
+ api_to_connect_tcp_await_timeout3(Socka).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the accept timeout option
+%% on an IPv4 TCP (stream) socket.
+api_to_accept_tcp4(suite) ->
+ [];
+api_to_accept_tcp4(doc) ->
+ [];
+api_to_accept_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_to_accept_tcp4,
+ fun() ->
+ InitState = #{domain => inet, timeout => 5000},
+ ok = api_to_accept_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the accept timeout option
+%% on an IPv6 TCP (stream) socket.
+api_to_accept_tcp6(suite) ->
+ [];
+api_to_accept_tcp6(doc) ->
+ [];
+api_to_accept_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_to_accept_tcp4,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet6, timeout => 5000},
+ ok = api_to_accept_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_to_accept_tcp(InitState) ->
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa => LSA}}
+ end},
+ #{desc => "create (listen) socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{lsock := LSock, lsa := LSA} = _State) ->
+ case sock_bind(LSock, LSA) of
+ {ok, _} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock)
+ end},
+
+ %% *** The actual test part ***
+ #{desc => "attempt to accept (without success)",
+ cmd => fun(#{lsock := LSock, timeout := To} = State) ->
+ Start = t(),
+ case socket:accept(LSock, To) of
+ {error, timeout} ->
+ {ok, State#{start => Start, stop => t()}};
+ {ok, Sock} ->
+ (catch socket:close(Sock)),
+ {error, unexpected_success};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "validate timeout time",
+ cmd => fun(#{start := Start, stop := Stop, timeout := To} = _State) ->
+ TDiff = Stop - Start,
+ if
+ (TDiff >= To) ->
+ ok;
+ true ->
+ {error, {unexpected_timeout, TDiff, To}}
+ end
+ end},
+
+ %% *** Close (listen) socket ***
+ #{desc => "close (listen) socket",
+ cmd => fun(#{lsock := LSock} = State) ->
+ sock_close(LSock),
+ {ok, maps:remove(sock3, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("create tester evaluator"),
+ Tester = ?SEV_START("tester", TesterSeq, InitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the multi accept timeout option
+%% on an IPv4 TCP (stream) socket with multiple acceptor processes
+%% (three in this case).
+api_to_maccept_tcp4(suite) ->
+ [];
+api_to_maccept_tcp4(doc) ->
+ [];
+api_to_maccept_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(20)),
+ tc_try(api_to_maccept_tcp4,
+ fun() ->
+ InitState = #{domain => inet, timeout => 5000},
+ ok = api_to_maccept_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the accept timeout option
+%% on an IPv6 TCP (stream) socket.
+api_to_maccept_tcp6(suite) ->
+ [];
+api_to_maccept_tcp6(doc) ->
+ [];
+api_to_maccept_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(20)),
+ tc_try(api_to_maccept_tcp4,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet6, timeout => 5000},
+ ok = api_to_maccept_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_to_maccept_tcp(InitState) ->
+ PrimAcceptorSeq =
+ [
+ %% *** Init part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa => LSA}}
+ end},
+ #{desc => "create (listen) socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{lsock := LSock, lsa := LSA} = _State) ->
+ case sock_bind(LSock, LSA) of
+ {ok, _} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{lsock := LSock, tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, LSock),
+ ok
+ end},
+
+ %% *** The actual test ***
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "attempt to accept (without success)",
+ cmd => fun(#{lsock := LSock, timeout := To} = State) ->
+ Start = t(),
+ case socket:accept(LSock, To) of
+ {error, timeout} ->
+ {ok, State#{start => Start, stop => t()}};
+ {ok, Sock} ->
+ ?SEV_EPRINT("Unexpected accept success: "
+ "~n ~p", [Sock]),
+ (catch socket:close(Sock)),
+ {error, unexpected_success};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "validate timeout time",
+ cmd => fun(#{start := Start, stop := Stop, timeout := To} = _State) ->
+ TDiff = Stop - Start,
+ if
+ (TDiff >= To) ->
+ ok;
+ true ->
+ {error, {unexpected_timeout, TDiff, To}}
+ end
+ end},
+ #{desc => "announce ready (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept),
+ ok
+ end},
+
+ %% *** Terminate ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_TERMINATE(Tester, tester),
+ ok
+ end},
+ %% *** Close (listen) socket ***
+ #{desc => "close (listen) socket",
+ cmd => fun(#{lsock := LSock} = State) ->
+ sock_close(LSock),
+ {ok, maps:remove(lsock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ SecAcceptorSeq =
+ [
+ %% *** Init part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ {Tester, LSock} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ lsock => LSock}}
+
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% *** The actual test part ***
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "attempt to accept (without success)",
+ cmd => fun(#{lsock := LSock, timeout := To} = State) ->
+ Start = t(),
+ case socket:accept(LSock, To) of
+ {error, timeout} ->
+ {ok, State#{start => Start, stop => t()}};
+ {ok, Sock} ->
+ (catch socket:close(Sock)),
+ {error, unexpected_success};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "validate timeout time",
+ cmd => fun(#{start := Start, stop := Stop, timeout := To} = State) ->
+ TDiff = Stop - Start,
+ if
+ (TDiff >= To) ->
+ State1 = maps:remove(start, State),
+ State2 = maps:remove(stop, State1),
+ {ok, State2};
+ true ->
+ {error, {unexpected_timeout, TDiff, To}}
+ end
+ end},
+ #{desc => "announce ready (accept)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept),
+ ok
+ end},
+
+ %% *** Terminate ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ TesterSeq =
+ [
+ %% Init part
+ #{desc => "monitor prim-acceptor",
+ cmd => fun(#{prim_acceptor := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor sec-acceptor 1",
+ cmd => fun(#{sec_acceptor1 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor sec-acceptor 2",
+ cmd => fun(#{sec_acceptor2 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+
+ %% Start the prim-acceptor
+ #{desc => "start prim-acceptor",
+ cmd => fun(#{prim_acceptor := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await prim-acceptor ready (init)",
+ cmd => fun(#{prim_acceptor := Pid} = State) ->
+ {ok, Sock} = ?SEV_AWAIT_READY(Pid, prim_acceptor, init),
+ {ok, State#{lsock => Sock}}
+ end},
+
+ %% Start sec-acceptor-1
+ #{desc => "start sec-acceptor 1",
+ cmd => fun(#{sec_acceptor1 := Pid, lsock := LSock} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, LSock),
+ ok
+ end},
+ #{desc => "await sec-acceptor 1 ready (init)",
+ cmd => fun(#{sec_acceptor1 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, sec_acceptor1, init)
+ end},
+
+ %% Start sec-acceptor-2
+ #{desc => "start sec-acceptor 2",
+ cmd => fun(#{sec_acceptor2 := Pid, lsock := LSock} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, LSock),
+ ok
+ end},
+ #{desc => "await sec-acceptor 2 ready (init)",
+ cmd => fun(#{sec_acceptor2 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, sec_acceptor2, init)
+ end},
+
+ %% Activate the acceptor(s)
+ #{desc => "active prim-acceptor",
+ cmd => fun(#{prim_acceptor := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, accept),
+ ok
+ end},
+ #{desc => "active sec-acceptor 1",
+ cmd => fun(#{sec_acceptor1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, accept),
+ ok
+ end},
+ #{desc => "active sec-acceptor 2",
+ cmd => fun(#{sec_acceptor2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, accept),
+ ok
+ end},
+
+ %% Await acceptor(s) completions
+ #{desc => "await prim-acceptor ready (accept)",
+ cmd => fun(#{prim_acceptor := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, prim_acceptor, accept)
+ end},
+ #{desc => "await sec-acceptor 1 ready (accept)",
+ cmd => fun(#{sec_acceptor1 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, sec_acceptor1, accept)
+ end},
+ #{desc => "await sec-acceptor 2 ready (accept)",
+ cmd => fun(#{sec_acceptor2 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, sec_acceptor2, accept)
+ end},
+
+ %% Terminate
+ #{desc => "order prim-acceptor to terminate",
+ cmd => fun(#{prim_acceptor := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await prim-acceptor termination",
+ cmd => fun(#{prim_acceptor := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(prim_acceptor, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order sec-acceptor 1 to terminate",
+ cmd => fun(#{sec_acceptor1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await sec-acceptor 1 termination",
+ cmd => fun(#{sec_acceptor1 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(sec_acceptor1, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order sec-acceptor 2 to terminate",
+ cmd => fun(#{sec_acceptor2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await sec-acceptor 2 termination",
+ cmd => fun(#{sec_acceptor2 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(sec_acceptor2, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("create prim-acceptor evaluator"),
+ PrimAInitState = InitState,
+ PrimAcceptor = ?SEV_START("prim-acceptor", PrimAcceptorSeq, PrimAInitState),
+
+ i("create sec-acceptor 1 evaluator"),
+ SecAInitState1 = maps:remove(domain, InitState),
+ SecAcceptor1 = ?SEV_START("sec-acceptor-1", SecAcceptorSeq, SecAInitState1),
+
+ i("create sec-acceptor 2 evaluator"),
+ SecAInitState2 = SecAInitState1,
+ SecAcceptor2 = ?SEV_START("sec-acceptor-2", SecAcceptorSeq, SecAInitState2),
+
+ i("create tester evaluator"),
+ TesterInitState = #{prim_acceptor => PrimAcceptor#ev.pid,
+ sec_acceptor1 => SecAcceptor1#ev.pid,
+ sec_acceptor2 => SecAcceptor2#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator(s)"),
+ ok = ?SEV_AWAIT_FINISH([PrimAcceptor, SecAcceptor1, SecAcceptor2, Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the send timeout option
+%% on an IPv4 TCP (stream) socket.
+api_to_send_tcp4(suite) ->
+ [];
+api_to_send_tcp4(doc) ->
+ [];
+api_to_send_tcp4(_Config) when is_list(_Config) ->
+ tc_try(api_to_send_tcp4,
+ fun() ->
+ not_yet_implemented()%% ,
+ %% ok = api_to_send_tcp(inet)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the send timeout option
+%% on an IPv6 TCP (stream) socket.
+api_to_send_tcp6(suite) ->
+ [];
+api_to_send_tcp6(doc) ->
+ [];
+api_to_send_tcp6(_Config) when is_list(_Config) ->
+ tc_try(api_to_send_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ not_yet_implemented()%% ,
+ %% ok = api_to_send_tcp(inet6)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the sendto timeout option
+%% on an IPv4 UDP (dgram) socket.
+api_to_sendto_udp4(suite) ->
+ [];
+api_to_sendto_udp4(doc) ->
+ [];
+api_to_sendto_udp4(_Config) when is_list(_Config) ->
+ tc_try(api_to_sendto_udp4,
+ fun() ->
+ not_yet_implemented()%% ,
+ %% ok = api_to_sendto_to_udp(inet)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the sendto timeout option
+%% on an IPv6 UDP (dgram) socket.
+api_to_sendto_udp6(suite) ->
+ [];
+api_to_sendto_udp6(doc) ->
+ [];
+api_to_sendto_udp6(_Config) when is_list(_Config) ->
+ tc_try(api_to_sendto_udp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ not_yet_implemented()%% ,
+ %% ok = api_to_sendto_to_udp(inet6)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the sendmsg timeout option
+%% on an IPv4 TCP (stream) socket.
+api_to_sendmsg_tcp4(suite) ->
+ [];
+api_to_sendmsg_tcp4(doc) ->
+ [];
+api_to_sendmsg_tcp4(_Config) when is_list(_Config) ->
+ tc_try(api_to_sendmsg_tcp4,
+ fun() ->
+ not_yet_implemented()%% ,
+ %% ok = api_to_sendmsg_tcp(inet)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the sendmsg timeout option
+%% on an IPv6 TCP (stream) socket.
+api_to_sendmsg_tcp6(suite) ->
+ [];
+api_to_sendmsg_tcp6(doc) ->
+ [];
+api_to_sendmsg_tcp6(_Config) when is_list(_Config) ->
+ tc_try(api_to_sendmsg_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ not_yet_implemented()%% ,
+ %% ok = api_to_sendmsg_tcp(inet6)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the recv timeout option
+%% on an IPv4 UDP (dgram) socket. To test this we must connect
+%% the socket.
+api_to_recv_udp4(suite) ->
+ [];
+api_to_recv_udp4(doc) ->
+ [];
+api_to_recv_udp4(_Config) when is_list(_Config) ->
+ tc_try(api_to_recv_udp4,
+ fun() ->
+ not_yet_implemented()%%,
+ %%ok = api_to_recv_udp(inet)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the recv timeout option
+%% on an IPv6 UDP (dgram) socket. To test this we must connect
+%% the socket.
+api_to_recv_udp6(suite) ->
+ [];
+api_to_recv_udp6(doc) ->
+ [];
+api_to_recv_udp6(_Config) when is_list(_Config) ->
+ tc_try(api_to_recv_udp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ not_yet_implemented()%% ,
+ %% ok = api_to_recv_udp(inet6)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the recv timeout option
+%% on an IPv4 TCP (stream) socket.
+api_to_recv_tcp4(suite) ->
+ [];
+api_to_recv_tcp4(doc) ->
+ [];
+api_to_recv_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_to_recv_tcp4,
+ fun() ->
+ Recv = fun(Sock, To) -> socket:recv(Sock, 0, To) end,
+ InitState = #{domain => inet,
+ recv => Recv,
+ timeout => 2000},
+ ok = api_to_receive_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the recv timeout option
+%% on an IPv6 TCP (stream) socket.
+api_to_recv_tcp6(suite) ->
+ [];
+api_to_recv_tcp6(doc) ->
+ [];
+api_to_recv_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_to_recv_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ case socket:is_supported(ipv6) of
+ true ->
+ Recv = fun(Sock, To) ->
+ socket:recv(Sock, 0, To)
+ end,
+ InitState = #{domain => inet6,
+ recv => Recv,
+ timeout => 2000},
+ ok = api_to_receive_tcp(InitState);
+ false ->
+ skip("ipv6 not supported")
+ end
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_to_receive_tcp(InitState) ->
+ process_flag(trap_exit, true),
+
+ ServerSeq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "create listen socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{lsock := LSock, local_sa := LSA} = State) ->
+ case sock_bind(LSock, LSA) of
+ {ok, Port} ->
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket (with backlog = 1)",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock, 1)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, lport := Port}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Port),
+ ok
+ end},
+
+ %% *** The actual test ***
+ #{desc => "await continue (accept and recv)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, accept_recv)
+ end},
+ #{desc => "attempt accept",
+ cmd => fun(#{lsock := LSock} = State) ->
+ case socket:accept(LSock) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "attempt to recv (without success)",
+ cmd => fun(#{sock := Sock, recv := Recv, timeout := To} = State) ->
+ Start = t(),
+ case Recv(Sock, To) of
+ {error, timeout} ->
+ {ok, State#{start => Start, stop => t()}};
+ {ok, _Data} ->
+ {error, unexpected_success};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "validate timeout time",
+ cmd => fun(#{start := Start, stop := Stop, timeout := To} = State) ->
+ TDiff = Stop - Start,
+ if
+ (TDiff >= To) ->
+ State1 = maps:remove(start, State),
+ State2 = maps:remove(stop, State1),
+ {ok, State2};
+ true ->
+ {error, {unexpected_timeout, TDiff, To}}
+ end
+ end},
+ #{desc => "announce ready (recv timeout success)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept_recv),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close (traffic) socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ sock_close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+ #{desc => "close (listen) socket",
+ cmd => fun(#{lsock := LSock} = State) ->
+ sock_close(LSock),
+ {ok, maps:remove(lsock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ ClientSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ {Tester, Port} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ server_port => Port}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain, server_port := Port} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ SSA = LSA#{port => Port},
+ {ok, State#{local_sa => LSA, server_sa => SSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{sock := Sock, local_sa := LSA} = _State) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% *** The actual test ***
+ #{desc => "await continue (with connect)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, connect)
+ end},
+ #{desc => "connect",
+ cmd => fun(#{sock := Sock, server_sa := SSA}) ->
+ sock_connect(Sock, SSA),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ sock_close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Server} = _State) ->
+ _MRef = erlang:monitor(process, Server),
+ ok
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Client} = _State) ->
+ _MRef = erlang:monitor(process, Client),
+ ok
+ end},
+
+ %% *** Activate server ***
+ #{desc => "start server",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_START(Server),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Server} = State) ->
+ {ok, Port} = ?SEV_AWAIT_READY(Server, server, init),
+ {ok, State#{server_port => Port}}
+ end},
+ #{desc => "order server to continue (with accept)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, accept_recv),
+ ok
+ end},
+
+ %% *** Activate client ***
+ #{desc => "start client",
+ cmd => fun(#{client := Client, server_port := Port} = _State) ->
+ ?SEV_ANNOUNCE_START(Client, Port),
+ ok
+ end},
+ #{desc => "await client ready (init)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, init)
+ end},
+
+ %% *** The actual test ***
+ #{desc => "order client to continue (with connect)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, connect),
+ ok
+ end},
+ #{desc => "await server ready (accept/recv)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, accept_recv)
+ end},
+
+ %% *** Termination ***
+ #{desc => "order client to terminate",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await client termination",
+ cmd => fun(#{client := Client} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Client) of
+ ok ->
+ State1 = maps:remove(client, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Server),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Server} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Server) of
+ ok ->
+ State1 = maps:remove(server, State),
+ State2 = maps:remove(server_port, State1),
+ {ok, State2};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ i("start server evaluator"),
+ ServerInitState = InitState,
+ Server = ?SEV_START("server", ServerSeq, ServerInitState),
+
+ i("start client evaluator"),
+ ClientInitState = InitState,
+ Client = ?SEV_START("client", ClientSeq, ClientInitState),
+
+ i("start tester evaluator"),
+ TesterInitState = #{server => Server#ev.pid,
+ client => Client#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator(s)"),
+ ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the recvfrom timeout option
+%% on an IPv4 UDP (dgram) socket.
+api_to_recvfrom_udp4(suite) ->
+ [];
+api_to_recvfrom_udp4(doc) ->
+ [];
+api_to_recvfrom_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_to_recvfrom_udp4,
+ fun() ->
+ Recv = fun(Sock, To) -> socket:recvfrom(Sock, 0, To) end,
+ InitState = #{domain => inet,
+ recv => Recv,
+ timeout => 2000},
+ ok = api_to_receive_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the recvfrom timeout option
+%% on an IPv6 UDP (dgram) socket.
+api_to_recvfrom_udp6(suite) ->
+ [];
+api_to_recvfrom_udp6(doc) ->
+ [];
+api_to_recvfrom_udp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_to_recvfrom_udp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ Recv = fun(Sock, To) -> socket:recvfrom(Sock, 0, To) end,
+ InitState = #{domain => inet6,
+ recv => Recv,
+ timeout => 2000},
+ ok = api_to_receive_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_to_receive_udp(InitState) ->
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa => LSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, dgram, udp) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{sock := Sock, lsa := LSA} = _State) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** The actual test ***
+ #{desc => "attempt to read (without success)",
+ cmd => fun(#{sock := Sock, recv := Recv, timeout := To} = State) ->
+ Start = t(),
+ case Recv(Sock, To) of
+ {error, timeout} ->
+ {ok, State#{start => Start,
+ stop => t()}};
+ {ok, _} ->
+ {error, unexpected_sucsess};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "validate timeout time",
+ cmd => fun(#{start := Start, stop := Stop, timeout := To} = _State) ->
+ TDiff = Stop - Start,
+ if
+ (TDiff >= To) ->
+ ok;
+ true ->
+ {error, {unexpected_timeout, TDiff, To}}
+ end
+ end},
+
+ %% *** Termination ***
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = _State) ->
+ %% socket:setopt(Sock, otp, debug, true),
+ sock_close(Sock),
+ ok
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start tester evaluator"),
+ Tester = ?SEV_START("tester", TesterSeq, InitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the recvmsg timeout option
+%% on an IPv4 UDP (dgram) socket.
+api_to_recvmsg_udp4(suite) ->
+ [];
+api_to_recvmsg_udp4(doc) ->
+ [];
+api_to_recvmsg_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_to_recvmsg_udp4,
+ fun() ->
+ Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end,
+ InitState = #{domain => inet,
+ recv => Recv,
+ timeout => 2000},
+ ok = api_to_receive_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the recvmsg timeout option
+%% on an IPv6 UDP (dgram) socket.
+api_to_recvmsg_udp6(suite) ->
+ [];
+api_to_recvmsg_udp6(doc) ->
+ [];
+api_to_recvmsg_udp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_to_recvmsg_udp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end,
+ InitState = #{domain => inet6,
+ recv => Recv,
+ timeout => 2000},
+ ok = api_to_receive_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the recvmsg timeout option
+%% on an IPv4 TCP (stream) socket.
+api_to_recvmsg_tcp4(suite) ->
+ [];
+api_to_recvmsg_tcp4(doc) ->
+ [];
+api_to_recvmsg_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_to_recvmsg_tcp4,
+ fun() ->
+ Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end,
+ InitState = #{domain => inet,
+ recv => Recv,
+ timeout => 2000},
+ ok = api_to_receive_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the recvmsg timeout option
+%% on an IPv6 TCP (stream) socket.
+api_to_recvmsg_tcp6(suite) ->
+ [];
+api_to_recvmsg_tcp6(doc) ->
+ [];
+api_to_recvmsg_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_to_recvmsg_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end,
+ InitState = #{domain => inet6,
+ recv => Recv,
+ timeout => 2000},
+ ok = api_to_receive_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% %%
+%% REGISTRY %%
+%% %%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% We create a bunch of different sockets and ensure that the registry
+%% has the correct info.
+
+reg_s_single_open_and_close_and_count(suite) ->
+ [];
+reg_s_single_open_and_close_and_count(doc) ->
+ [];
+reg_s_single_open_and_close_and_count(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(reg_s_single_open_and_close_and_count,
+ fun() ->
+ ok = reg_s_single_open_and_close_and_count()
+ end).
+
+
+reg_s_single_open_and_close_and_count() ->
+ SupportsIPV6 =
+ case (catch has_support_ipv6()) of
+ ok ->
+ true;
+ _ ->
+ false
+ end,
+ SupportsLOCAL =
+ case (catch has_support_unix_domain_socket()) of
+ ok ->
+ true;
+ _ ->
+ false
+ end,
+ SupportsSCTP =
+ case (catch has_support_sctp()) of
+ ok ->
+ true;
+ _ ->
+ false
+ end,
+ InitSockInfos =
+ [
+ {inet, stream, tcp},
+ {inet, dgram, udp}
+ ] ++
+ case SupportsIPV6 of
+ true ->
+ [
+ {inet6, stream, tcp},
+ {inet6, dgram, udp}
+ ];
+ false ->
+ []
+ end ++
+ case SupportsLOCAL of
+ true ->
+ [
+ {local, stream, default},
+ {local, dgram, default}
+ ];
+ false ->
+ []
+ end ++
+ [
+ {inet, stream, tcp},
+ {inet, dgram, udp}
+ ] ++
+ case SupportsSCTP of
+ true ->
+ [
+ {inet, seqpacket, sctp},
+ {inet, seqpacket, sctp}
+ ];
+ false ->
+ []
+ end ++
+ [
+ {inet, stream, tcp},
+ {inet, dgram, udp}
+ ] ++
+ case SupportsSCTP andalso SupportsIPV6 of
+ true ->
+ [
+ {inet6, seqpacket, sctp},
+ {inet6, seqpacket, sctp}
+ ];
+ false ->
+ []
+ end,
+
+ i("open sockets"),
+ Socks =
+ [fun({Domain, Type, Proto}) ->
+ i("open socket: ~w, ~w, ~w", [Domain, Type, Proto]),
+ {ok, Sock} = socket:open(Domain, Type, Proto),
+ Sock
+ end(InitSockInfo) || InitSockInfo <- InitSockInfos],
+
+ ?SLEEP(1000),
+
+
+ %% **** Total Number Of Sockets ****
+
+ NumSocks1 = length(Socks),
+ NumberOf1 = socket:number_of(),
+
+ i("verify (total) number of sockets(1): ~w, ~w", [NumSocks1, NumberOf1]),
+ case (NumSocks1 =:= NumberOf1) of
+ true ->
+ ok;
+ false ->
+ exit({wrong_number_of_sockets1, NumSocks1, NumberOf1})
+ end,
+
+
+ %% **** Number Of IPv4 TCP Sockets ****
+
+ %% inet, stream, tcp
+ SiNumTCP = reg_si_num(InitSockInfos, inet, stream, tcp),
+ SrNumTCP = reg_sr_num(inet, stream, tcp),
+
+ i("verify number of IPv4 TCP sockets: ~w, ~w", [SiNumTCP, SrNumTCP]),
+ case (SiNumTCP =:= SrNumTCP) of
+ true ->
+ ok;
+ false ->
+ exit({wrong_number_of_ipv4_tcp_sockets, SiNumTCP, SrNumTCP})
+ end,
+
+
+ %% **** Number Of IPv4 UDP Sockets ****
+
+ %% inet, dgram, udp
+ SiNumUDP = reg_si_num(InitSockInfos, inet, dgram, udp),
+ SrNumUDP = reg_sr_num(inet, dgram, udp),
+
+ i("verify number of IPv4 UDP sockets: ~w, ~w", [SiNumUDP, SrNumUDP]),
+ case (SiNumUDP =:= SrNumUDP) of
+ true ->
+ ok;
+ false ->
+ exit({wrong_number_of_ipv4_udp_sockets, SiNumUDP, SrNumUDP})
+ end,
+
+
+ %% **** Number Of IPv4 SCTP Sockets ****
+
+ %% inet, seqpacket, sctp
+ SiNumSCTP = reg_si_num(InitSockInfos, inet, seqpacket, sctp),
+ SrNumSCTP = reg_sr_num(inet, seqpacket, sctp),
+
+ i("verify number of IPv4 SCTP sockets: ~w, ~w", [SiNumSCTP, SrNumSCTP]),
+ case (SiNumSCTP =:= SrNumSCTP) of
+ true ->
+ ok;
+ false ->
+ exit({wrong_number_of_sctp_sockets, SiNumSCTP, SrNumSCTP})
+ end,
+
+
+ %% **** Number Of IPv4 Sockets ****
+
+ %% inet
+ SiNumINET = reg_si_num(InitSockInfos, inet),
+ SrNumINET = reg_sr_num(inet),
+
+ i("verify number of IPv4 sockets: ~w, ~w", [SiNumINET, SrNumINET]),
+ case (SiNumINET =:= SrNumINET) of
+ true ->
+ ok;
+ false ->
+ exit({wrong_number_of_ipv4_sockets, SiNumINET, SrNumINET})
+ end,
+
+
+ %% **** Number Of IPv6 Sockets ****
+
+ %% inet6
+ SiNumINET6 = reg_si_num(InitSockInfos, inet6),
+ SrNumINET6 = reg_sr_num(inet6),
+
+ i("verify number of IPv6 sockets: ~w, ~w", [SiNumINET6, SrNumINET6]),
+ case (SiNumINET6 =:= SrNumINET6) of
+ true ->
+ ok;
+ false ->
+ exit({wrong_number_of_ipv6_sockets, SiNumINET6, SrNumINET6})
+ end,
+
+
+ %% **** Number Of Unix Domain Sockets Sockets ****
+
+ %% local
+ SiNumLOCAL = reg_si_num(InitSockInfos, local),
+ SrNumLOCAL = reg_sr_num(local),
+
+ i("verify number of Unix Domain Sockets sockets: ~w, ~w",
+ [SiNumLOCAL, SrNumLOCAL]),
+ case (SiNumLOCAL =:= SrNumLOCAL) of
+ true ->
+ ok;
+ false ->
+ exit({wrong_number_of_local_sockets, SiNumLOCAL, SrNumLOCAL})
+ end,
+
+
+ %% **** Close *all* Sockets then verify Number Of Sockets ****
+
+ i("close sockets"),
+ lists:foreach(fun(S) ->
+ i("close socket"),
+ ok = socket:close(S)
+ end, Socks),
+
+ ?SLEEP(1000),
+
+ NumSocks2 = 0,
+ NumberOf2 = socket:number_of(),
+
+ i("verify number of sockets(2): ~w, ~w", [NumSocks2, NumberOf2]),
+ case (NumSocks2 =:= NumberOf2) of
+ true ->
+ ok;
+ false ->
+ exit({wrong_number_of_sockets2, NumSocks2, NumberOf2})
+ end,
+
+ ok.
+
+
+reg_si_num(SocksInfo, Domain)
+ when ((Domain =:= inet) orelse (Domain =:= inet6) orelse (Domain =:= local)) ->
+ reg_si_num(SocksInfo, Domain, undefined, undefined);
+reg_si_num(SocksInfo, Type)
+ when ((Type =:= stream) orelse (Type =:= dgram) orelse (Type =:= seqpacket)) ->
+ reg_si_num(SocksInfo, undefined, Type, undefined);
+reg_si_num(SocksInfo, Proto)
+ when ((Proto =:= sctp) orelse (Proto =:= tcp) orelse (Proto =:= udp)) ->
+ reg_si_num(SocksInfo, undefined, undefined, Proto).
+
+reg_si_num(SocksInfo, Domain, undefined, undefined) ->
+ F = fun({D, _T, _P}) when (D =:= Domain) -> true;
+ (_) -> false
+ end,
+ reg_si_num2(F, SocksInfo);
+reg_si_num(SocksInfo, undefined, Type, undefined) ->
+ F = fun({_D, T, _P}) when (T =:= Type) -> true;
+ (_) -> false
+ end,
+ reg_si_num2(F, SocksInfo);
+reg_si_num(SocksInfo, undefined, undefined, Proto) ->
+ F = fun({_D, _T, P}) when (P =:= Proto) -> true;
+ (_) -> false
+ end,
+ reg_si_num2(F, SocksInfo);
+reg_si_num(SocksInfo, Domain, Type, Proto) ->
+ F = fun({D, T, P}) when (D =:= Domain) andalso
+ (T =:= Type) andalso
+ (P =:= Proto) ->
+ true;
+ (_) ->
+ false
+ end,
+ reg_si_num2(F, SocksInfo).
+
+reg_si_num2(F, SocksInfo) ->
+ length(lists:filter(F, SocksInfo)).
+
+
+reg_sr_num(Domain)
+ when ((Domain =:= inet) orelse (Domain =:= inet6)) ->
+ length(socket:which_sockets(Domain));
+reg_sr_num(Domain)
+ when (Domain =:= local) ->
+ reg_sr_num(Domain, undefined, undefined);
+reg_sr_num(Type)
+ when ((Type =:= stream) orelse (Type =:= dgram) orelse (Type =:= seqpacket)) ->
+ length(socket:which_sockets(Type));
+reg_sr_num(Proto)
+ when ((Proto =:= sctp) orelse (Proto =:= tcp) orelse (Proto =:= udp)) ->
+ length(socket:which_sockets(Proto)).
+
+reg_sr_num(Domain, undefined, undefined) ->
+ F = fun(#{domain := D}) when (D =:= Domain) ->
+ true;
+ (_X) ->
+ false
+ end,
+ reg_sr_num2(F);
+reg_sr_num(Domain, Type, Proto) ->
+ F = fun(#{domain := D,
+ type := T,
+ protocol := P}) when (D =:= Domain) andalso
+ (T =:= Type) andalso
+ (P =:= Proto) ->
+ true;
+ (_X) ->
+ %% i("reg_sr_num -> not counting: "
+ %% "~n ~p", [_X]),
+ false
+ end,
+ reg_sr_num2(F).
+
+reg_sr_num2(F) ->
+ length(socket:which_sockets(F)).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% %%
+%% SOCKET CLOSURE %%
+%% %%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sockets are cleaned up
+%% ("removed") when the controlling process terminates (without explicitly
+%% calling the close function). For a IPv4 TCP (stream) socket.
+
+sc_cpe_socket_cleanup_tcp4(suite) ->
+ [];
+sc_cpe_socket_cleanup_tcp4(doc) ->
+ [];
+sc_cpe_socket_cleanup_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(30)),
+ tc_try(sc_cpe_socket_cleanup_tcp4,
+ fun() ->
+ InitState = #{domain => inet,
+ type => stream,
+ protocol => tcp},
+ ok = sc_cpe_socket_cleanup(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sockets are cleaned up
+%% ("removed") when the controlling process terminates (without explicitly
+%% calling the close function). For a IPv6 TCP (stream) socket.
+
+sc_cpe_socket_cleanup_tcp6(suite) ->
+ [];
+sc_cpe_socket_cleanup_tcp6(doc) ->
+ [];
+sc_cpe_socket_cleanup_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(30)),
+ tc_try(sc_cpe_socket_cleanup_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet6,
+ type => stream,
+ protocol => tcp},
+ ok = sc_cpe_socket_cleanup(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sockets are cleaned up
+%% ("removed") when the controlling process terminates (without explicitly
+%% calling the close function). For a Unix Domain (stream) socket (TCP).
+
+sc_cpe_socket_cleanup_tcpL(suite) ->
+ [];
+sc_cpe_socket_cleanup_tcpL(doc) ->
+ [];
+sc_cpe_socket_cleanup_tcpL(_Config) when is_list(_Config) ->
+ ?TT(?SECS(30)),
+ tc_try(sc_cpe_socket_cleanup_tcpL,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ InitState = #{domain => local,
+ type => stream,
+ protocol => default},
+ ok = sc_cpe_socket_cleanup(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sockets are cleaned up
+%% ("removed") when the controlling process terminates (without explicitly
+%% calling the close function). For a IPv4 UDP (dgram) socket.
+
+sc_cpe_socket_cleanup_udp4(suite) ->
+ [];
+sc_cpe_socket_cleanup_udp4(doc) ->
+ [];
+sc_cpe_socket_cleanup_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(30)),
+ tc_try(sc_cpe_socket_cleanup_udp4,
+ fun() ->
+ InitState = #{domain => inet,
+ type => dgram,
+ protocol => udp},
+ ok = sc_cpe_socket_cleanup(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sockets are cleaned up
+%% (removed) when the controlling process terminates (without explicitly
+%% calling the close function). For a IPv6 UDP (dgram) socket.
+
+sc_cpe_socket_cleanup_udp6(suite) ->
+ [];
+sc_cpe_socket_cleanup_udp6(doc) ->
+ [];
+sc_cpe_socket_cleanup_udp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(30)),
+ tc_try(sc_cpe_socket_cleanup_udp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet6,
+ type => dgram,
+ protocol => udp},
+ ok = sc_cpe_socket_cleanup(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sockets are cleaned up
+%% ("removed") when the controlling process terminates (without explicitly
+%% calling the close function). For a Unix Domain (dgram) socket (UDP).
+
+sc_cpe_socket_cleanup_udpL(suite) ->
+ [];
+sc_cpe_socket_cleanup_udpL(doc) ->
+ [];
+sc_cpe_socket_cleanup_udpL(_Config) when is_list(_Config) ->
+ ?TT(?SECS(30)),
+ tc_try(sc_cpe_socket_cleanup_udpL,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ InitState = #{domain => local,
+ type => dgram,
+ protocol => default},
+ ok = sc_cpe_socket_cleanup(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+sc_cpe_socket_cleanup(InitState) ->
+ OwnerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Sock),
+ ok
+ end},
+
+ %% *** The actual test ***
+ %% We *intentially* leave the socket "as is", no explicit close
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor owner",
+ cmd => fun(#{owner := Owner} = _State) ->
+ _MRef = erlang:monitor(process, Owner),
+ ok
+ end},
+ #{desc => "order (owner) start",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await (owner) ready",
+ cmd => fun(#{owner := Pid} = State) ->
+ {ok, Sock} = ?SEV_AWAIT_READY(Pid, owner, init),
+ {ok, State#{sock => Sock}}
+ end},
+ #{desc => "verify owner as controlling-process",
+ cmd => fun(#{owner := Pid, sock := Sock} = _State) ->
+ case socket:getopt(Sock, otp, controlling_process) of
+ {ok, Pid} ->
+ ok;
+ {ok, Other} ->
+ {error, {unexpected_owner, Other}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order (owner) terminate",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await (owner) termination",
+ cmd => fun(#{owner := Pid} = _State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ ?SEV_SLEEP(?SECS(5)),
+
+ %% The reason we get closed, is that as long as there is a ref to
+ %% the resource (socket), then it will not be garbage collected.
+ %% Note that its still a race that the nif has processed that the
+ %% "controlling process" has terminated. There really is no
+ %% proper timeout for this, but the 5 seconds "should" be enough...
+ %% We should really have some way to subscribe to socket events...
+ #{desc => "verify no socket (closed)",
+ cmd => fun(#{owner := Pid, sock := Sock} = _State) ->
+ case socket:getopt(Sock, otp, controlling_process) of
+ {ok, OtherPid} ->
+ {error, {unexpected_success, Pid, OtherPid}};
+ {error, closed} ->
+ ok;
+ {error, Reason} ->
+ ?SEV_IPRINT("expected failure: ~p", [Reason]),
+ {error, {unexpected_failure, Reason}}
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start (socket) owner evaluator"),
+ Owner = ?SEV_START("owner", OwnerSeq, InitState),
+
+ i("start tester evaluator"),
+ TesterInitState = #{owner => Owner#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Owner, Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% locally closed while a process is calling the recv function.
+%% Socket is IPv4.
+%%
+%% <KOLLA>
+%%
+%% We should really have a similar test cases for when the controlling
+%% process exits and there are other processes in recv, accept, and
+%% all the other functions.
+%%
+%% </KOLLA>
+
+sc_lc_recv_response_tcp4(suite) ->
+ [];
+sc_lc_recv_response_tcp4(doc) ->
+ [];
+sc_lc_recv_response_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(sc_lc_recv_response_tcp4,
+ fun() ->
+ Recv = fun(Sock) -> socket:recv(Sock) end,
+ InitState = #{domain => inet,
+ protocol => tcp,
+ recv => Recv},
+ ok = sc_lc_receive_response_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% locally closed while the process is calling the recv function.
+%% Socket is IPv6.
+
+sc_lc_recv_response_tcp6(suite) ->
+ [];
+sc_lc_recv_response_tcp6(doc) ->
+ [];
+sc_lc_recv_response_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(sc_lc_recv_response_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ Recv = fun(Sock) -> socket:recv(Sock) end,
+ InitState = #{domain => inet6,
+ protocol => tcp,
+ recv => Recv},
+ ok = sc_lc_receive_response_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% locally closed while the process is calling the recv function.
+%% Socket is Unix Domain (stream) socket.
+
+sc_lc_recv_response_tcpL(suite) ->
+ [];
+sc_lc_recv_response_tcpL(doc) ->
+ [];
+sc_lc_recv_response_tcpL(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(sc_lc_recv_response_tcpL,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ Recv = fun(Sock) -> socket:recv(Sock) end,
+ InitState = #{domain => local,
+ protocol => default,
+ recv => Recv},
+ ok = sc_lc_receive_response_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+sc_lc_receive_response_tcp(InitState) ->
+ %% This (acceptor) is the server that accepts connections.
+ %% But it is also suppose to close the connection socket,
+ %% and trigger the read failure (=closed) for the handler process.
+ AcceptorSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa => LSA}}
+ end},
+ #{desc => "create (listen) socket",
+ cmd => fun(#{domain := Domain,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, stream, Proto) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{domain := local,
+ lsock := LSock,
+ lsa := LSA} = _State) ->
+ ?SEV_IPRINT("bind to LSA: "
+ "~n ~p", [LSA]),
+ case socket:bind(LSock, LSA) of
+ {ok, _Port} ->
+ ok; % We do not care about the port for local
+ {error, _} = ERROR ->
+ ERROR
+ end;
+ (#{lsock := LSock,
+ lsa := LSA} = State) ->
+ case sock_bind(LSock, LSA) of
+ {ok, Port} ->
+ ?SEV_IPRINT("bound to port: ~w", [Port]),
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{domain := local,
+ tester := Tester,
+ lsa := #{path := Path}}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Path),
+ ok;
+ (#{tester := Tester, lport := Port}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Port),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_CONTINUE(Tester, tester, accept) of
+ {ok, {H1, H2, H3}} ->
+ {ok, State#{handler1 => H1,
+ handler2 => H2,
+ handler3 => H3}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "await accept",
+ cmd => fun(#{lsock := LSock} = State) ->
+ case socket:accept(LSock) of
+ {ok, Sock} ->
+ ?SEV_IPRINT("connection accepted: "
+ "~n ~p", [socket:sockname(Sock)]),
+ {ok, State#{csock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept),
+ ok
+ end},
+ #{desc => "transfer connection to handler 1",
+ cmd => fun(#{handler1 := Handler, csock := Sock}) ->
+ ?SEV_ANNOUNCE_CONTINUE(Handler, transfer, Sock),
+ ok
+ end},
+ #{desc => "transfer connection to handler 2",
+ cmd => fun(#{handler2 := Handler, csock := Sock}) ->
+ ?SEV_ANNOUNCE_CONTINUE(Handler, transfer, Sock),
+ ok
+ end},
+ #{desc => "transfer connection to handler 3",
+ cmd => fun(#{handler3 := Handler, csock := Sock}) ->
+ ?SEV_ANNOUNCE_CONTINUE(Handler, transfer, Sock),
+ ok
+ end},
+ #{desc => "await continue (close)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, close),
+ ok
+ end},
+ #{desc => "close connection socket",
+ cmd => fun(#{csock := Sock} = State) ->
+ case socket:close(Sock) of
+ ok ->
+ {ok, maps:remove(csock, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (close)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, close),
+ ok
+ end},
+
+ %% *** Terminate ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close listen socket",
+ cmd => fun(#{domain := local,
+ lsock := Sock,
+ lsa := #{path := Path}} = State) ->
+ ok = socket:close(Sock),
+ State1 =
+ unlink_path(Path,
+ fun() ->maps:remove(lsa, State) end,
+ fun() -> State end),
+ State2 = maps:remove(lsock, State1),
+ State3 = maps:remove(lport, State2),
+ {ok, State3};
+ (#{lsock := Sock} = State) ->
+ case socket:close(Sock) of
+ ok ->
+ State1 = maps:remove(lsock, State),
+ State2 = maps:remove(lport, State1),
+ {ok, State2};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ %% The point of this is to perform the recv for which
+ %% we are testing the reponse.
+ HandlerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ {Tester, Acceptor} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ acceptor => Acceptor}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+ #{desc => "monitor acceptor",
+ cmd => fun(#{acceptor := Acceptor} = _State) ->
+ _MRef = erlang:monitor(process, Acceptor),
+ ok
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (transfer)",
+ cmd => fun(#{acceptor := Pid} = State) ->
+ case ?SEV_AWAIT_CONTINUE(Pid, acceptor, transfer) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (transfer)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, transfer),
+ ok
+ end},
+ #{desc => "attempt recv (=> closed)",
+ cmd => fun(#{sock := Sock, recv := Recv} = State) ->
+ %% ok = socket:setopt(Sock, otp, debug, true),
+ case Recv(Sock) of
+ {ok, _Data} ->
+ ?SEV_EPRINT("Unexpected data received"),
+ {error, unexpected_success};
+ {error, closed} ->
+ ?SEV_IPRINT("received expected 'closed' "
+ "result"),
+ State1 = maps:remove(sock, State),
+ {ok, State1};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected read failure: "
+ "~n ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv closed)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_closed),
+ ok
+ end},
+
+ %% *** Terminate ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ %% The point of this is basically just to create the connection.
+ ClientSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, stream, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind socket to local address",
+ cmd => fun(#{sock := Sock, local_sa := LSA} = _State) ->
+ ?SEV_IPRINT("bind to LSA: "
+ "~n ~p", [LSA]),
+ case sock_bind(Sock, LSA) of
+ {ok, _} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (connect)",
+ cmd => fun(#{domain := local = Domain,
+ tester := Tester} = State) ->
+ case ?SEV_AWAIT_CONTINUE(Tester, tester, connect) of
+ {ok, ServerPath} ->
+ ?SEV_IPRINT("Server Path: "
+ "~n ~s", [ServerPath]),
+ ServerSA = #{family => Domain,
+ path => ServerPath},
+ {ok, State#{server_sa => ServerSA}};
+ {error, _} = ERROR ->
+ ERROR
+ end;
+ (#{tester := Tester, local_sa := LSA} = State) ->
+ case ?SEV_AWAIT_CONTINUE(Tester, tester, connect) of
+ {ok, Port} ->
+ ServerSA = LSA#{port => Port},
+ {ok, State#{server_sa => ServerSA}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "connect to server",
+ cmd => fun(#{sock := Sock, server_sa := ServerSA}) ->
+ socket:connect(Sock, ServerSA)
+ end},
+ #{desc => "announce ready (connect)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_ANNOUNCE_READY(Tester, connect),
+ ok
+ end},
+
+ %% *** Terminate ***
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{domain := local,
+ sock := Sock,
+ local_sa := #{path := Path}} = State) ->
+ sock_close(Sock),
+ State1 =
+ unlink_path(Path,
+ fun() ->
+ maps:remove(local_sa, State)
+ end,
+ fun() -> State end),
+ {ok, maps:remove(sock, State1)};
+ (#{sock := Sock} = State) ->
+ sock_close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor acceptor",
+ cmd => fun(#{acceptor := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor handler 1",
+ cmd => fun(#{handler1 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor handler 2",
+ cmd => fun(#{handler2 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor handler 3",
+ cmd => fun(#{handler3 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the acceptor
+ #{desc => "order acceptor start",
+ cmd => fun(#{acceptor := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await acceptor ready (init)",
+ cmd => fun(#{acceptor := Pid} = State) ->
+ case ?SEV_AWAIT_READY(Pid, acceptor, init) of
+ {ok, PortOrPath} ->
+ {ok, State#{server_info => PortOrPath}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% Start the handler(s)
+ #{desc => "order handler 1 start",
+ cmd => fun(#{acceptor := Acceptor, handler1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Acceptor),
+ ok
+ end},
+ #{desc => "await handler 1 ready (init)",
+ cmd => fun(#{handler1 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, handler1, init)
+ end},
+ #{desc => "order handler 2 start",
+ cmd => fun(#{acceptor := Acceptor, handler2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Acceptor),
+ ok
+ end},
+ #{desc => "await handler 2 ready (init)",
+ cmd => fun(#{handler2 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, handler2, init)
+ end},
+ #{desc => "order handler 3 start",
+ cmd => fun(#{acceptor := Acceptor, handler3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Acceptor),
+ ok
+ end},
+ #{desc => "await handler 3 ready (init)",
+ cmd => fun(#{handler3 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, handler3, init)
+ end},
+
+ %% Start the client
+ #{desc => "order client start",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await client ready (init)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, client, init)
+ end},
+
+ %% The actual test
+ #{desc => "order acceptor to continue (accept)",
+ cmd => fun(#{acceptor := Pid,
+ handler1 := H1,
+ handler2 := H2,
+ handler3 := H3} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, accept, {H1, H2, H3}),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order client to continue (connect)",
+ cmd => fun(#{client := Pid, server_info := Info} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, connect, Info),
+ ok
+ end},
+ #{desc => "await acceptor ready (accept)",
+ cmd => fun(#{acceptor := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, acceptor, accept)
+ end},
+ #{desc => "await client ready (connect)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client, connect)
+ end},
+ #{desc => "await handler 1 ready (transfer)",
+ cmd => fun(#{handler1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, handler1, transfer)
+ end},
+ #{desc => "await handler 2 ready (transfer)",
+ cmd => fun(#{handler2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, handler2, transfer)
+ end},
+ #{desc => "await handler 3 ready (transfer)",
+ cmd => fun(#{handler3 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, handler3, transfer)
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order acceptor to continue (close connection socket)",
+ cmd => fun(#{acceptor := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, close),
+ ok
+ end},
+ #{desc => "await acceptor ready (close)",
+ cmd => fun(#{acceptor := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, acceptor, close)
+ end},
+ #{desc => "await handler 1 ready (recv closed)",
+ cmd => fun(#{handler1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, handler1, recv_closed)
+ end},
+ #{desc => "await handler 2 ready (recv closed)",
+ cmd => fun(#{handler2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, handler2, recv_closed)
+ end},
+ #{desc => "await handler 3 ready (recv closed)",
+ cmd => fun(#{handler3 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, handler3, recv_closed)
+ end},
+
+ %% Terminations
+ #{desc => "order client to terminate",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await client termination",
+ cmd => fun(#{client := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(client, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order handler 1 to terminate",
+ cmd => fun(#{handler1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await handler 1 termination",
+ cmd => fun(#{handler1 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(handler1, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order handler 2 to terminate",
+ cmd => fun(#{handler2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await handler 2 termination",
+ cmd => fun(#{handler2 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(handler2, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order handler 3 to terminate",
+ cmd => fun(#{handler3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await handler 3 termination",
+ cmd => fun(#{handler3 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(handler3, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order acceptor to terminate",
+ cmd => fun(#{acceptor := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await acceptor termination",
+ cmd => fun(#{acceptor := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(acceptor, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start acceptor evaluator"),
+ AccInitState = InitState,
+ Acceptor = ?SEV_START("acceptor", AcceptorSeq, AccInitState),
+
+ i("start handler 1 evaluator"),
+ HandlerInitState = #{recv => maps:get(recv, InitState)},
+ Handler1 = ?SEV_START("handler-1", HandlerSeq, HandlerInitState),
+
+ i("start handler 2 evaluator"),
+ Handler2 = ?SEV_START("handler-2", HandlerSeq, HandlerInitState),
+
+ i("start handler 3 evaluator"),
+ Handler3 = ?SEV_START("handler-3", HandlerSeq, HandlerInitState),
+
+ i("start client evaluator"),
+ ClientInitState = InitState,
+ Client = ?SEV_START("client", ClientSeq, ClientInitState),
+
+ i("start tester evaluator"),
+ TesterInitState = #{acceptor => Acceptor#ev.pid,
+ handler1 => Handler1#ev.pid,
+ handler2 => Handler2#ev.pid,
+ handler3 => Handler3#ev.pid,
+ client => Client#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Acceptor,
+ Handler1, Handler2, Handler3,
+ Client, Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% locally closed while a process is calling the recvfrom function.
+%% Socket is IPv4.
+%%
+
+sc_lc_recvfrom_response_udp4(suite) ->
+ [];
+sc_lc_recvfrom_response_udp4(doc) ->
+ [];
+sc_lc_recvfrom_response_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(30)),
+ tc_try(sc_lc_recvfrom_response_udp4,
+ fun() ->
+ Recv = fun(Sock, To) -> socket:recvfrom(Sock, [], To) end,
+ InitState = #{domain => inet,
+ protocol => udp,
+ recv => Recv},
+ ok = sc_lc_receive_response_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% locally closed while the process is calling the recv function.
+%% Socket is IPv6.
+
+sc_lc_recvfrom_response_udp6(suite) ->
+ [];
+sc_lc_recvfrom_response_udp6(doc) ->
+ [];
+sc_lc_recvfrom_response_udp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(30)),
+ tc_try(sc_lc_recvfrom_response_udp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ Recv = fun(Sock, To) -> socket:recvfrom(Sock, [], To) end,
+ InitState = #{domain => inet6,
+ protocol => udp,
+ recv => Recv},
+ ok = sc_lc_receive_response_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% locally closed while the process is calling the recv function.
+%% Socket is Unix Domainm (dgram) socket.
+
+sc_lc_recvfrom_response_udpL(suite) ->
+ [];
+sc_lc_recvfrom_response_udpL(doc) ->
+ [];
+sc_lc_recvfrom_response_udpL(_Config) when is_list(_Config) ->
+ ?TT(?SECS(30)),
+ tc_try(sc_lc_recvfrom_response_udpL,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ Recv = fun(Sock, To) ->
+ socket:recvfrom(Sock, [], To)
+ end,
+ InitState = #{domain => local,
+ protocol => default,
+ recv => Recv},
+ ok = sc_lc_receive_response_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+sc_lc_receive_response_udp(InitState) ->
+ PrimServerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "open socket",
+ cmd => fun(#{domain := Domain, protocol := Proto} = State) ->
+ Sock = sock_open(Domain, dgram, Proto),
+ %% SA = sock_sockname(Sock),
+ case socket:sockname(Sock) of
+ {ok, SA} ->
+ {ok, State#{sock => Sock, sa => SA}};
+ {error, eafnosupport = Reason} ->
+ ?SEV_IPRINT("Failed get socket name: "
+ "~n ~p", [Reason]),
+ (catch socket:close(Sock)),
+ {skip, Reason};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed get socket name: "
+ "~n ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "bind socket",
+ cmd => fun(#{sock := Sock, local_sa := LSA}) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ?SEV_IPRINT("src bound"),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("src bind failed: ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, sock := Sock}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Sock),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (recv, with timeout)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_CONTINUE(Tester, tester, recv) of
+ {ok, Timeout} ->
+ {ok, State#{timeout => Timeout}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "receive, with timeout",
+ cmd => fun(#{sock := Sock, recv := Recv, timeout := Timeout}) ->
+ case Recv(Sock, Timeout) of
+ {error, timeout} ->
+ ok;
+ {ok, _} ->
+ {error, unexpected_success};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv, with timeout)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv),
+ ok
+ end},
+ #{desc => "await continue (close)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ok = ?SEV_AWAIT_CONTINUE(Tester, tester, close)
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{domain := local,
+ sock := Sock,
+ local_sa := #{path := Path}} = State) ->
+ ok = socket:close(Sock),
+ State1 =
+ unlink_path(Path,
+ fun() ->
+ maps:remove(local_sa, State)
+ end,
+ fun() -> State end),
+ {ok, maps:remove(sock, State1)};
+ (#{sock := Sock} = State) ->
+ case socket:close(Sock) of
+ ok ->
+ {ok, maps:remove(sock, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (close)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, close),
+ ok
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, terminate) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ SecServerSeq =
+ [
+ %% *** Init part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ {Tester, Sock} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, sock => Sock}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (recv)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ok = ?SEV_AWAIT_CONTINUE(Tester, tester, recv)
+
+ end},
+ #{desc => "receive",
+ cmd => fun(#{sock := Sock, recv := Recv} = State) ->
+ case Recv(Sock, infinity) of
+ {error, closed} ->
+ {ok, maps:remove(sock, State)};
+ {ok, _} ->
+ {error, unexpected_success};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv closed)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_closed),
+ ok
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor primary server",
+ cmd => fun(#{prim_server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor secondary server 1",
+ cmd => fun(#{sec_server1 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor secondary server 2",
+ cmd => fun(#{sec_server2 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor secondary server 3",
+ cmd => fun(#{sec_server3 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the primary server
+ #{desc => "order 'primary server' start",
+ cmd => fun(#{prim_server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await 'primary server' ready (init)",
+ cmd => fun(#{prim_server := Pid} = State) ->
+ {ok, Sock} = ?SEV_AWAIT_READY(Pid, prim_server, init),
+ {ok, State#{sock => Sock}}
+ end},
+
+ %% Start the secondary server 1
+ #{desc => "order 'secondary server 1' start",
+ cmd => fun(#{sec_server1 := Pid, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Sock),
+ ok
+ end},
+ #{desc => "await 'secondary server 1' ready (init)",
+ cmd => fun(#{sec_server1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, sec_server1, init)
+ end},
+
+ %% Start the secondary server 2
+ #{desc => "order 'secondary server 2' start",
+ cmd => fun(#{sec_server2 := Pid, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Sock),
+ ok
+ end},
+ #{desc => "await 'secondary server 2' ready (init)",
+ cmd => fun(#{sec_server2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, sec_server2, init)
+ end},
+
+ %% Start the secondary server 3
+ #{desc => "order 'secondary server 3' start",
+ cmd => fun(#{sec_server3 := Pid, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Sock),
+ ok
+ end},
+ #{desc => "await 'secondary server 3' ready (init)",
+ cmd => fun(#{sec_server3 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, sec_server3, init)
+ end},
+
+
+ %% The actual test
+ %% Make all the seondary servers continue, with an infinit recvfrom
+ %% and then the prim-server with a timed recvfrom.
+ %% After the prim server notifies us (about the timeout) we order it
+ %% to close the socket, which should cause the all the secondary
+ %% server to return with error-closed.
+
+ #{desc => "order 'secondary server 1' to continue (recv)",
+ cmd => fun(#{sec_server1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order 'secondary server 2' to continue (recv)",
+ cmd => fun(#{sec_server2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order 'secondary server 3' to continue (recv)",
+ cmd => fun(#{sec_server3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order 'primary server' to continue (recv, with timeout)",
+ cmd => fun(#{prim_server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv, ?SECS(5)),
+ ok
+ end},
+ #{desc => "await 'primary server' ready (recv, with timeout)",
+ cmd => fun(#{prim_server := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, prim_server, recv)
+ end},
+ #{desc => "order 'primary server' to continue (close)",
+ cmd => fun(#{prim_server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, close),
+ ok
+ end},
+ #{desc => "await 'primary server' ready (close)",
+ cmd => fun(#{prim_server := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, prim_server, close)
+ end},
+ #{desc => "await 'secondary server 1' ready (closed)",
+ cmd => fun(#{sec_server1 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, sec_server1, recv_closed)
+ end},
+ #{desc => "await 'secondary server 2' ready (closed)",
+ cmd => fun(#{sec_server2 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, sec_server2, recv_closed)
+ end},
+ #{desc => "await 'secondary server 3' ready (closed)",
+ cmd => fun(#{sec_server3 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, sec_server3, recv_closed)
+ end},
+
+ %% Terminations
+ #{desc => "order 'secondary server 3' to terminate",
+ cmd => fun(#{sec_server3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await 'secondary server 3' termination",
+ cmd => fun(#{sec_server3 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(sec_server3, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order 'secondary server 2' to terminate",
+ cmd => fun(#{sec_server2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await 'secondary server 2' termination",
+ cmd => fun(#{sec_server2 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(sec_server2, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order 'secondary server 1' to terminate",
+ cmd => fun(#{sec_server1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await 'secondary server 1' termination",
+ cmd => fun(#{sec_server1 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(sec_server1, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order 'primary server' to terminate",
+ cmd => fun(#{prim_server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await 'primary server' termination",
+ cmd => fun(#{prim_server := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(prim_server, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ i("start 'primary server' evaluator"),
+ PrimSrvInitState = InitState,
+ PrimServer = ?SEV_START("prim-server", PrimServerSeq, PrimSrvInitState),
+
+ i("start 'secondary server 1' evaluator"),
+ SecSrvInitState = #{recv => maps:get(recv, InitState)},
+ SecServer1 = ?SEV_START("sec-server-1", SecServerSeq, SecSrvInitState),
+
+ i("start 'secondary server 2' evaluator"),
+ SecServer2 = ?SEV_START("sec-server-2", SecServerSeq, SecSrvInitState),
+
+ i("start 'secondary server 3' evaluator"),
+ SecServer3 = ?SEV_START("sec-server-3", SecServerSeq, SecSrvInitState),
+
+ i("start 'tester' evaluator"),
+ TesterInitState = #{prim_server => PrimServer#ev.pid,
+ sec_server1 => SecServer1#ev.pid,
+ sec_server2 => SecServer2#ev.pid,
+ sec_server3 => SecServer3#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([PrimServer,
+ SecServer1, SecServer2, SecServer3,
+ Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% locally closed while the process is calling the recvmsg function.
+%% Socket is IPv4.
+
+sc_lc_recvmsg_response_tcp4(suite) ->
+ [];
+sc_lc_recvmsg_response_tcp4(doc) ->
+ [];
+sc_lc_recvmsg_response_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(sc_lc_recvmsg_response_tcp4,
+ fun() ->
+ Recv = fun(Sock) -> socket:recvmsg(Sock) end,
+ InitState = #{domain => inet,
+ protocol => tcp,
+ recv => Recv},
+ ok = sc_lc_receive_response_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% locally closed while the process is calling the recvmsg function.
+%% Socket is IPv6.
+
+sc_lc_recvmsg_response_tcp6(suite) ->
+ [];
+sc_lc_recvmsg_response_tcp6(doc) ->
+ [];
+sc_lc_recvmsg_response_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(sc_recvmsg_response_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ Recv = fun(Sock) -> socket:recvmsg(Sock) end,
+ InitState = #{domain => inet6,
+ protocol => tcp,
+ recv => Recv},
+ ok = sc_lc_receive_response_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% locally closed while the process is calling the recvmsg function.
+%% Socket is Unix Domain (stream) socket.
+
+sc_lc_recvmsg_response_tcpL(suite) ->
+ [];
+sc_lc_recvmsg_response_tcpL(doc) ->
+ [];
+sc_lc_recvmsg_response_tcpL(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(sc_recvmsg_response_tcpL,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ Recv = fun(Sock) -> socket:recvmsg(Sock) end,
+ InitState = #{domain => local,
+ protocol => default,
+ recv => Recv},
+ ok = sc_lc_receive_response_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% locally closed while the process is calling the recvmsg function.
+%% Socket is IPv4.
+
+sc_lc_recvmsg_response_udp4(suite) ->
+ [];
+sc_lc_recvmsg_response_udp4(doc) ->
+ [];
+sc_lc_recvmsg_response_udp4(_Config) when is_list(_Config) ->
+ tc_try(sc_lc_recvmsg_response_udp4,
+ fun() ->
+ ?TT(?SECS(10)),
+ Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end,
+ InitState = #{domain => inet,
+ protocol => udp,
+ recv => Recv},
+ ok = sc_lc_receive_response_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% locally closed while the process is calling the recvmsg function.
+%% Socket is IPv6.
+
+sc_lc_recvmsg_response_udp6(suite) ->
+ [];
+sc_lc_recvmsg_response_udp6(doc) ->
+ [];
+sc_lc_recvmsg_response_udp6(_Config) when is_list(_Config) ->
+ tc_try(sc_recvmsg_response_udp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ ?TT(?SECS(10)),
+ Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end,
+ InitState = #{domain => inet6,
+ protocol => udp,
+ recv => Recv},
+ ok = sc_lc_receive_response_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% locally closed while the process is calling the recvmsg function.
+%% Socket is Unix Domain (dgram) socket.
+
+sc_lc_recvmsg_response_udpL(suite) ->
+ [];
+sc_lc_recvmsg_response_udpL(doc) ->
+ [];
+sc_lc_recvmsg_response_udpL(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(sc_recvmsg_response_udpL,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end,
+ InitState = #{domain => local,
+ protocol => default,
+ recv => Recv},
+ ok = sc_lc_receive_response_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% locally closed while the process is calling the accept function.
+%% We test what happens with a non-controlling_process also, since we
+%% git the setup anyway.
+%% Socket is IPv4.
+
+sc_lc_acceptor_response_tcp4(suite) ->
+ [];
+sc_lc_acceptor_response_tcp4(doc) ->
+ [];
+sc_lc_acceptor_response_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(sc_lc_acceptor_response_tcp4,
+ fun() ->
+ InitState = #{domain => inet,
+ protocol => tcp},
+ ok = sc_lc_acceptor_response_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% locally closed while the process is calling the accept function.
+%% We test what happens with a non-controlling_process also, since we
+%% git the setup anyway.
+%% Socket is IPv6.
+
+sc_lc_acceptor_response_tcp6(suite) ->
+ [];
+sc_lc_acceptor_response_tcp6(doc) ->
+ [];
+sc_lc_acceptor_response_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(sc_lc_acceptor_response_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet6,
+ protocol => tcp},
+ ok = sc_lc_acceptor_response_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% locally closed while the process is calling the accept function.
+%% We test what happens with a non-controlling_process also, since we
+%% git the setup anyway.
+%% Socket is Unix Domain (stream) socket.
+
+sc_lc_acceptor_response_tcpL(suite) ->
+ [];
+sc_lc_acceptor_response_tcpL(doc) ->
+ [];
+sc_lc_acceptor_response_tcpL(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(sc_lc_acceptor_response_tcpL,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ InitState = #{domain => local,
+ protocol => default},
+ ok = sc_lc_acceptor_response_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+sc_lc_acceptor_response_tcp(InitState) ->
+ PrimAcceptorSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa => LSA}}
+ end},
+ #{desc => "create (listen) socket",
+ cmd => fun(#{domain := Domain,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, stream, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{sock := Sock, lsa := LSA} = _State) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{sock := Sock}) ->
+ socket:listen(Sock)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Sock),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_CONTINUE(Tester, tester, accept) of
+ {ok, Timeout} ->
+ {ok, State#{timeout => Timeout}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "await connection",
+ cmd => fun(#{sock := LSock, timeout := Timeout} = _State) ->
+ case socket:accept(LSock, Timeout) of
+ {error, timeout} ->
+ ok;
+ {ok, Sock} ->
+ ?SEV_EPRINT("unexpected success"),
+ (catch socket:close(Sock)),
+ {error, unexpected_success};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (accept timeout)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept_timeout),
+ ok
+ end},
+ #{desc => "await continue (close)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ok = ?SEV_AWAIT_CONTINUE(Tester, tester, close)
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{domain := local,
+ sock := Sock,
+ lsa := #{path := Path}} = State) ->
+ case socket:close(Sock) of
+ ok ->
+ State1 =
+ unlink_path(Path,
+ fun() ->
+ maps:remove(lsa, State)
+ end,
+ fun() ->
+ State
+ end),
+ {ok, maps:remove(sock, State1)};
+ {error, _} = ERROR ->
+ unlink_path(Path),
+ ERROR
+ end;
+ (#{sock := Sock} = State) ->
+ case socket:close(Sock) of
+ ok ->
+ {ok, maps:remove(sock, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (close)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, close),
+ ok
+ end},
+
+ % Termination
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ SecAcceptorSeq =
+ [
+ %% *** Init part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ {Tester, Sock} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, sock => Sock}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init)
+ end},
+
+ %% The actual test
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ok = ?SEV_AWAIT_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "accept",
+ cmd => fun(#{sock := Sock} = State) ->
+ case socket:accept(Sock) of
+ {error, closed} ->
+ {ok, maps:remove(sock, State)};
+ {ok, _} ->
+ {error, unexpected_success};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (accept closed)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept_closed)
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor 'primary acceptor'",
+ cmd => fun(#{prim_acc := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor 'secondary acceptor 1'",
+ cmd => fun(#{sec_acc1 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor secondary acceptor 2",
+ cmd => fun(#{sec_acc2 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor secondary acceptor 3",
+ cmd => fun(#{sec_acc3 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the primary server
+ #{desc => "order 'primary acceptor' start",
+ cmd => fun(#{prim_acc := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await 'primary acceptor' ready (init)",
+ cmd => fun(#{prim_acc := Pid} = State) ->
+ {ok, Sock} = ?SEV_AWAIT_READY(Pid, prim_acc, init),
+ {ok, State#{sock => Sock}}
+ end},
+
+ %% Start the secondary acceptor 1
+ #{desc => "order 'secondary acceptor 1' start",
+ cmd => fun(#{sec_acc1 := Pid, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Sock),
+ ok
+ end},
+ #{desc => "await 'secondary acceptor 1' ready (init)",
+ cmd => fun(#{sec_acc1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, sec_acc1, init)
+ end},
+
+ %% Start the secondary acceptor 2
+ #{desc => "order 'secondary acceptor 2' start",
+ cmd => fun(#{sec_acc2 := Pid, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Sock),
+ ok
+ end},
+ #{desc => "await 'secondary acceptor 2' ready (init)",
+ cmd => fun(#{sec_acc2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, sec_acc2, init)
+ end},
+
+ %% Start the secondary acceptor 3
+ #{desc => "order 'secondary acceptor 3' start",
+ cmd => fun(#{sec_acc3 := Pid, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Sock),
+ ok
+ end},
+ #{desc => "await 'secondary acceptor 3' ready (init)",
+ cmd => fun(#{sec_acc3 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, sec_acc3, init)
+ end},
+
+
+ %% The actual test
+ %% Make all the seondary servers continue, with an infinit recvfrom
+ %% and then the prim-server with a timed recvfrom.
+ %% After the prim server notifies us (about the timeout) we order it
+ %% to close the socket, which should cause the all the secondary
+ %% server to return with error-closed.
+
+ #{desc => "order 'secondary acceptor 1' to continue (accept)",
+ cmd => fun(#{sec_acc1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, accept),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order 'secondary acceptor 2' to continue (accept)",
+ cmd => fun(#{sec_acc2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, accept),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order 'secondary acceptor 3' to continue (accept)",
+ cmd => fun(#{sec_acc3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, accept),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order 'primary acceptor' to continue",
+ cmd => fun(#{prim_acc := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, accept, ?SECS(5)),
+ ok
+ end},
+ #{desc => "await 'primary acceptor' ready (accept timeout)",
+ cmd => fun(#{prim_acc := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, prim_acc, accept_timeout)
+ end},
+ #{desc => "order 'primary acceptor' to continue (close)",
+ cmd => fun(#{prim_acc := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, close),
+ ok
+ end},
+ #{desc => "await 'primary acceptor' ready (close)",
+ cmd => fun(#{prim_acc := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, prim_acc, close)
+ end},
+ #{desc => "await 'secondary acceptor 1' ready (accept closed)",
+ cmd => fun(#{sec_acc1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, sec_acc1, accept_closed)
+ end},
+ #{desc => "await 'secondary acceptor 2' ready (accept closed)",
+ cmd => fun(#{sec_acc2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, sec_acc2, accept_closed)
+ end},
+ #{desc => "await 'secondary acceptor 3' ready (accept closed)",
+ cmd => fun(#{sec_acc3 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, sec_acc3, accept_closed)
+ end},
+
+
+ %% Terminations
+ #{desc => "order 'secondary acceptor 3' to terminate",
+ cmd => fun(#{sec_acc3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await 'secondary acceptor 3' termination",
+ cmd => fun(#{sec_acc3 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(sec_acc3, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order 'secondary acceptor 2' to terminate",
+ cmd => fun(#{sec_acc2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await 'secondary acceptor 2' termination",
+ cmd => fun(#{sec_acc2 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(sec_acc2, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order 'secondary acceptor 1' to terminate",
+ cmd => fun(#{sec_acc1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await 'secondary acceptor 1' termination",
+ cmd => fun(#{sec_acc1 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(sec_acc1, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order 'primary acceptor' to terminate",
+ cmd => fun(#{prim_acc := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await 'primary acceptor' termination",
+ cmd => fun(#{prim_acc := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(prim_acc, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ i("start 'primary acceptor' evaluator"),
+ PrimAccInitState = InitState,
+ PrimAcc = ?SEV_START("prim-acceptor", PrimAcceptorSeq, PrimAccInitState),
+
+ i("start 'secondary acceptor 1' evaluator"),
+ SecAccInitState = #{},
+ SecAcc1 = ?SEV_START("sec-acceptor-1", SecAcceptorSeq, SecAccInitState),
+
+ i("start 'secondary acceptor 2' evaluator"),
+ SecAcc2 = ?SEV_START("sec-acceptor-2", SecAcceptorSeq, SecAccInitState),
+
+ i("start 'secondary acceptor 3' evaluator"),
+ SecAcc3 = ?SEV_START("sec-acceptor-3", SecAcceptorSeq, SecAccInitState),
+
+ i("start 'tester' evaluator"),
+ TesterInitState = #{prim_acc => PrimAcc#ev.pid,
+ sec_acc1 => SecAcc1#ev.pid,
+ sec_acc2 => SecAcc2#ev.pid,
+ sec_acc3 => SecAcc3#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([PrimAcc, SecAcc1, SecAcc2, SecAcc3, Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% remotely closed while the process is calling the recv function.
+%% Socket is IPv4.
+%%
+%% To minimize the chance of "weirdness", we should really have test cases
+%% where the two sides of the connection is on different machines. But for
+%% now, we will make do with different VMs on the same host.
+%%
+
+sc_rc_recv_response_tcp4(suite) ->
+ [];
+sc_rc_recv_response_tcp4(doc) ->
+ [];
+sc_rc_recv_response_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(30)),
+ tc_try(sc_rc_recv_response_tcp4,
+ fun() ->
+ Recv = fun(Sock) -> socket:recv(Sock) end,
+ InitState = #{domain => inet,
+ protocol => tcp,
+ recv => Recv},
+ ok = sc_rc_receive_response_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% remotely closed while the process is calling the recv function.
+%% Socket is IPv6.
+
+sc_rc_recv_response_tcp6(suite) ->
+ [];
+sc_rc_recv_response_tcp6(doc) ->
+ [];
+sc_rc_recv_response_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(30)),
+ tc_try(sc_rc_recv_response_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ Recv = fun(Sock) -> socket:recv(Sock) end,
+ InitState = #{domain => inet6,
+ protocol => tcp,
+ recv => Recv},
+ ok = sc_rc_receive_response_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% remotely closed while the process is calling the recv function.
+%% Socket is Unix Domain (stream) socket.
+
+sc_rc_recv_response_tcpL(suite) ->
+ [];
+sc_rc_recv_response_tcpL(doc) ->
+ [];
+sc_rc_recv_response_tcpL(_Config) when is_list(_Config) ->
+ ?TT(?SECS(30)),
+ tc_try(sc_rc_recv_response_tcpL,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ Recv = fun(Sock) -> socket:recv(Sock) end,
+ InitState = #{domain => local,
+ protocol => default,
+ recv => Recv},
+ ok = sc_rc_receive_response_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+sc_rc_receive_response_tcp(InitState) ->
+ %% Each connection are handled by handler processes.
+ %% These are created (on the fly) and handled internally
+ %% by the server!
+ ServerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "create listen socket",
+ cmd => fun(#{domain := Domain, protocol := Proto} = State) ->
+ case socket:open(Domain, stream, Proto) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{domain := local,
+ lsock := LSock,
+ lsa := LSA} = _State) ->
+ case socket:bind(LSock, LSA) of
+ {ok, _Port} ->
+ ok; % We do not care about the port for local
+ {error, _} = ERROR ->
+ ERROR
+ end;
+ (#{lsock := LSock,
+ local_sa := LSA} = State) ->
+ case sock_bind(LSock, LSA) of
+ {ok, Port} ->
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{domain := local,
+ tester := Tester,
+ local_sa := LSA}) ->
+ %% Actually we only need to send the path,
+ %% but to keep it simple, we send the "same"
+ %% as for non-local.
+ ?SEV_ANNOUNCE_READY(Tester, init, LSA),
+ ok;
+ (#{tester := Tester, local_sa := LSA, lport := Port}) ->
+ ServerSA = LSA#{port => Port},
+ ?SEV_ANNOUNCE_READY(Tester, init, ServerSA),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (accept all three connections)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "accept 1",
+ cmd => fun(#{lsock := LSock, recv := Recv} = State) ->
+ case socket:accept(LSock) of
+ {ok, Sock} ->
+ ?SEV_IPRINT("accepted: try start handler"),
+ Handler = sc_rc_tcp_handler_start(1, Recv, Sock),
+ ?SEV_IPRINT("handler started"),
+ {ok, State#{csock1 => Sock,
+ handler1 => Handler}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "await handler 1 ready (init)",
+ cmd => fun(#{tester := Tester,
+ handler1 := Handler1} = _State) ->
+ ?SEV_AWAIT_READY(Handler1, handler1, init,
+ [{tester, Tester}])
+ end},
+ #{desc => "accept 2",
+ cmd => fun(#{lsock := LSock, recv := Recv} = State) ->
+ case socket:accept(LSock) of
+ {ok, Sock} ->
+ ?SEV_IPRINT("accepted: try start handler"),
+ Handler = sc_rc_tcp_handler_start(2, Recv, Sock),
+ ?SEV_IPRINT("handler started"),
+ {ok, State#{csock2 => Sock,
+ handler2 => Handler}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "await handler 2 ready (init)",
+ cmd => fun(#{tester := Tester,
+ handler1 := Handler1,
+ handler2 := Handler2} = _State) ->
+ ?SEV_AWAIT_READY(Handler2, handler2, init,
+ [{tester, Tester},
+ {handler1, Handler1}])
+ end},
+ #{desc => "accept 3",
+ cmd => fun(#{lsock := LSock, recv := Recv} = State) ->
+ case socket:accept(LSock) of
+ {ok, Sock} ->
+ ?SEV_IPRINT("accepted: try start handler"),
+ Handler = sc_rc_tcp_handler_start(3, Recv, Sock),
+ ?SEV_IPRINT("handler started"),
+ {ok, State#{csock3 => Sock,
+ handler3 => Handler}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "await handler 3 ready (init)",
+ cmd => fun(#{tester := Tester,
+ handler1 := Handler1,
+ handler2 := Handler2,
+ handler3 := Handler3} = _State) ->
+ ?SEV_AWAIT_READY(Handler3, handler3, init,
+ [{tester, Tester},
+ {handler1, Handler1},
+ {handler2, Handler2}])
+ end},
+ #{desc => "announce ready (accept all three connections)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept),
+ ok
+ end},
+ #{desc => "await continue (recv)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv)
+ end},
+ #{desc => "order handler 1 to receive",
+ cmd => fun(#{handler1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ #{desc => "order handler 2 to receive",
+ cmd => fun(#{handler2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ #{desc => "order handler 3 to receive",
+ cmd => fun(#{handler3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ #{desc => "await ready from handler 1 (recv)",
+ cmd => fun(#{tester := Tester, handler1 := Pid} = _State) ->
+ case ?SEV_AWAIT_READY(Pid, handler1, recv,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ Result;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "await ready from handler 2 (recv)",
+ cmd => fun(#{tester := Tester, handler2 := Pid} = _State) ->
+ case ?SEV_AWAIT_READY(Pid, handler2, recv,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ Result;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "await ready from handler 3 (recv)",
+ cmd => fun(#{tester := Tester, handler3 := Pid} = _State) ->
+ case ?SEV_AWAIT_READY(Pid, handler3, recv,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ Result;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv closed from all handlers)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_closed),
+ ok
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order handler 1 to terminate",
+ cmd => fun(#{handler1 := Pid} = _State) ->
+ %% Pid ! {terminate, self(), ok},
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await handler 1 termination",
+ cmd => fun(#{handler1 := Pid} = State) ->
+ ?SEV_AWAIT_TERMINATION(Pid),
+ State1 = maps:remove(csock1, State),
+ State2 = maps:remove(handler1, State1),
+ {ok, State2}
+ end},
+ #{desc => "order handler 2 to terminate",
+ cmd => fun(#{handler2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await handler 2 termination",
+ cmd => fun(#{handler2 := Pid} = State) ->
+ ?SEV_AWAIT_TERMINATION(Pid),
+ State1 = maps:remove(csock2, State),
+ State2 = maps:remove(handler2, State1),
+ {ok, State2}
+ end},
+ #{desc => "order handler 3 to terminate",
+ cmd => fun(#{handler3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await handler 3 termination",
+ cmd => fun(#{handler3 := Pid} = State) ->
+ ?SEV_AWAIT_TERMINATION(Pid),
+ State1 = maps:remove(csock3, State),
+ State2 = maps:remove(handler3, State1),
+ {ok, State2}
+ end},
+ #{desc => "close listen socket",
+ cmd => fun(#{domain := local,
+ lsock := LSock,
+ lsa := #{path := Path}} = State) ->
+ case socket:close(LSock) of
+ ok ->
+ State1 =
+ unlink_path(Path,
+ fun() ->
+ maps:remove(lsa, State)
+ end,
+ fun() ->
+ State
+ end),
+ {ok, maps:remove(lsock, State1)};
+ {error, _} = ERROR ->
+ unlink_path(Path),
+ ERROR
+ end;
+ (#{lsock := LSock} = State) ->
+ case socket:close(LSock) of
+ ok ->
+ {ok, maps:remove(lsock, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ ClientSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ {Tester, {NodeID, ServerSA}} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ node_id => NodeID,
+ server_sa => ServerSA}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "create node",
+ cmd => fun(#{host := Host, node_id := NodeID} = State) ->
+ case start_node(Host, l2a(f("client_~w", [NodeID]))) of
+ {ok, Node} ->
+ ?SEV_IPRINT("client node ~p started", [Node]),
+ {ok, State#{node => Node}};
+ {error, Reason} ->
+ {skip, Reason}
+ end
+ end},
+ #{desc => "monitor client node 1",
+ cmd => fun(#{node := Node} = _State) ->
+ true = erlang:monitor_node(Node, true),
+ ok
+ end},
+ #{desc => "start remote client on client node",
+ cmd => fun(#{node := Node} = State) ->
+ Pid = sc_rc_tcp_client_start(Node),
+ ?SEV_IPRINT("client ~p started", [Pid]),
+ {ok, State#{rclient => Pid}}
+ end},
+ #{desc => "monitor remote client",
+ cmd => fun(#{rclient := Pid}) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order remote client to start",
+ cmd => fun(#{rclient := Client,
+ server_sa := ServerSA,
+ protocol := Proto}) ->
+ ?SEV_ANNOUNCE_START(Client, {ServerSA, Proto}),
+ ok
+ end},
+ #{desc => "await remote client ready",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, rclient, init,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (connect)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, connect,
+ [{rclient, Client}]),
+ ok
+ end},
+ #{desc => "order remote client to continue (connect)",
+ cmd => fun(#{rclient := Client}) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, connect),
+ ok
+ end},
+ #{desc => "await client process ready (connect)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, rclient, connect,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (connected)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, connect),
+ ok
+ end},
+ #{desc => "await continue (close)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, close,
+ [{rclient, Client}]),
+ ok
+ end},
+ #{desc => "order remote client to close",
+ cmd => fun(#{rclient := Client}) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, close),
+ ok
+ end},
+ #{desc => "await remote client ready (closed)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, rclient, close,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (close)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, close),
+ ok
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester,
+ [{rclient, Client}]) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "kill remote client",
+ cmd => fun(#{rclient := Client}) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await remote client termination",
+ cmd => fun(#{rclient := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(rclient, State),
+ {ok, State1}
+ end},
+ #{desc => "stop client node",
+ cmd => fun(#{node := Node} = _State) ->
+ stop_node(Node)
+ end},
+ #{desc => "await client node termination",
+ cmd => fun(#{node := Node} = State) ->
+ receive
+ {nodedown, Node} ->
+ State1 = maps:remove(node_id, State),
+ State2 = maps:remove(node, State1),
+ {ok, State2}
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client 1",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client 2",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client 3",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = State) ->
+ {ok, ServerSA} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{server_sa => ServerSA}}
+ end},
+
+ %% Start the client(s)
+ #{desc => "order client 1 start",
+ cmd => fun(#{client1 := Pid,
+ server_sa := ServerSA} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, {1, ServerSA}),
+ ok
+ end},
+ #{desc => "await client 1 ready (init)",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client1, init)
+ end},
+ #{desc => "order client 2 start",
+ cmd => fun(#{client2 := Pid,
+ server_sa := ServerSA} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, {2, ServerSA}),
+ ok
+ end},
+ #{desc => "await client 2 ready (init)",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client2, init)
+ end},
+ #{desc => "order client 3 start",
+ cmd => fun(#{client3 := Pid,
+ server_sa := ServerSA} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, {3, ServerSA}),
+ ok
+ end},
+ #{desc => "await client 3 ready (init)",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client3, init)
+ end},
+
+ %% The actual test
+ #{desc => "order server continue (accept)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, accept),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order client 1 continue (connect)",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, connect),
+ ok
+ end},
+ #{desc => "await client 1 ready (connect)",
+ cmd => fun(#{server := Server,
+ client1 := Client1,
+ client2 := Client2,
+ client3 := Client3} = _State) ->
+ ?SEV_AWAIT_READY(Client1, client1, connect,
+ [{server, Server},
+ {client2, Client2},
+ {client3, Client3}]),
+ ok
+ end},
+ #{desc => "order client 2 continue (connect)",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, connect),
+ ok
+ end},
+ #{desc => "await client 2 ready (connect)",
+ cmd => fun(#{server := Server,
+ client1 := Client1,
+ client2 := Client2,
+ client3 := Client3} = _State) ->
+ ?SEV_AWAIT_READY(Client2, client2, connect,
+ [{server, Server},
+ {client1, Client1},
+ {client3, Client3}]),
+ ok
+ end},
+ #{desc => "order client 3 continue (connect)",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, connect),
+ ok
+ end},
+ #{desc => "await client 3 ready (connect)",
+ cmd => fun(#{server := Server,
+ client1 := Client1,
+ client2 := Client2,
+ client3 := Client3} = _State) ->
+ ?SEV_AWAIT_READY(Client3, client3, connect,
+ [{server, Server},
+ {client1, Client1},
+ {client2, Client2}]),
+ ok
+ end},
+ #{desc => "await server ready (accept from all connections)",
+ cmd => fun(#{server := Server,
+ client1 := Client1,
+ client2 := Client2,
+ client3 := Client3} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, accept,
+ [{client1, Client1},
+ {client2, Client2},
+ {client3, Client3}]),
+ ok
+ end},
+ #{desc => "order server continue (recv for all connections)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order client 1 continue (close)",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, close),
+ ok
+ end},
+ #{desc => "await client 1 ready (close)",
+ cmd => fun(#{server := Server,
+ client1 := Client1,
+ client2 := Client2,
+ client3 := Client3} = _State) ->
+ ?SEV_AWAIT_READY(Client1, client1, close,
+ [{server, Server},
+ {client2, Client2},
+ {client3, Client3}]),
+ ok
+ end},
+ #{desc => "order client 2 continue (close)",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, close),
+ ok
+ end},
+ #{desc => "await client 2 ready (close)",
+ cmd => fun(#{server := Server,
+ client1 := Client1,
+ client2 := Client2,
+ client3 := Client3} = _State) ->
+ ?SEV_AWAIT_READY(Client2, client2, close,
+ [{server, Server},
+ {client1, Client1},
+ {client3, Client3}]),
+ ok
+ end},
+ #{desc => "order client 3 continue (close)",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, close),
+ ok
+ end},
+ #{desc => "await client 3 ready (close)",
+ cmd => fun(#{server := Server,
+ client1 := Client1,
+ client2 := Client2,
+ client3 := Client3} = _State) ->
+ ?SEV_AWAIT_READY(Client3, client1, close,
+ [{server, Server},
+ {client1, Client1},
+ {client2, Client2}]),
+ ok
+ end},
+ #{desc => "await server ready (close for all connections)",
+ cmd => fun(#{server := Server,
+ client1 := Client1,
+ client2 := Client2,
+ client3 := Client3} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv_closed,
+ [{client1, Client1},
+ {client2, Client2},
+ {client3, Client3}]),
+ ok
+ end},
+
+ %% Terminations
+ #{desc => "order client 1 to terminate",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await client 1 termination",
+ cmd => fun(#{client1 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(client1, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order client 2 to terminate",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await client 2 termination",
+ cmd => fun(#{client2 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(client2, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order client 3 to terminate",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await client 3 termination",
+ cmd => fun(#{client3 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(client3, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(server, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start server evaluator"),
+ ServerInitState = InitState,
+ Server = ?SEV_START("server", ServerSeq, ServerInitState),
+
+ i("start client evaluator(s)"),
+ ClientInitState = InitState#{host => local_host()},
+ Client1 = ?SEV_START("client-1", ClientSeq, ClientInitState),
+ Client2 = ?SEV_START("client-2", ClientSeq, ClientInitState),
+ Client3 = ?SEV_START("client-3", ClientSeq, ClientInitState),
+
+ i("start 'tester' evaluator"),
+ TesterInitState = #{server => Server#ev.pid,
+ client1 => Client1#ev.pid,
+ client2 => Client2#ev.pid,
+ client3 => Client3#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Server,
+ Client1, Client2, Client3,
+ Tester]).
+
+
+sc_rc_tcp_client_start(Node) ->
+ Self = self(),
+ Fun = fun() -> sc_rc_tcp_client(Self) end,
+ erlang:spawn(Node, Fun).
+
+
+sc_rc_tcp_client(Parent) ->
+ sc_rc_tcp_client_init(Parent),
+ {ServerSA, Proto} = sc_rc_tcp_client_await_start(Parent),
+ Domain = maps:get(family, ServerSA),
+ Sock = sc_rc_tcp_client_create(Domain, Proto),
+ Path = sc_rc_tcp_client_bind(Sock, Domain),
+ sc_rc_tcp_client_announce_ready(Parent, init),
+ sc_rc_tcp_client_await_continue(Parent, connect),
+ sc_rc_tcp_client_connect(Sock, ServerSA),
+ sc_rc_tcp_client_announce_ready(Parent, connect),
+ sc_rc_tcp_client_await_continue(Parent, close),
+ sc_rc_tcp_client_close(Sock, Path),
+ sc_rc_tcp_client_announce_ready(Parent, close),
+ Reason = sc_rc_tcp_client_await_terminate(Parent),
+ ?SEV_IPRINT("terminate"),
+ exit(Reason).
+
+sc_rc_tcp_client_init(Parent) ->
+ put(sname, "rclient"),
+ ?SEV_IPRINT("init"),
+ _MRef = erlang:monitor(process, Parent),
+ ok.
+
+sc_rc_tcp_client_await_start(Parent) ->
+ i("sc_rc_tcp_client_await_start -> entry"),
+ ?SEV_AWAIT_START(Parent).
+
+sc_rc_tcp_client_create(Domain, Proto) ->
+ i("sc_rc_tcp_client_create -> entry"),
+ case socket:open(Domain, stream, Proto) of
+ {ok, Sock} ->
+ case socket:getopt(Sock, otp, fd) of
+ {ok, FD} ->
+ put(sname, f("rclient-~w", [FD])); % Update SName
+ _ ->
+ ok
+ end,
+ Sock;
+ {error, Reason} ->
+ exit({open_failed, Reason})
+ end.
+
+sc_rc_tcp_client_bind(Sock, Domain) ->
+ i("sc_rc_tcp_client_bind -> entry"),
+ LSA = which_local_socket_addr(Domain),
+ case socket:bind(Sock, LSA) of
+ {ok, _} ->
+ case socket:sockname(Sock) of
+ {ok, #{family := local, path := Path}} ->
+ Path;
+ {ok, _} ->
+ undefined;
+ {error, Reason1} ->
+ exit({sockname, Reason1})
+ end;
+ {error, Reason} ->
+ exit({bind, Reason})
+ end.
+
+sc_rc_tcp_client_announce_ready(Parent, Slogan) ->
+ ?SEV_IPRINT("ready ~w", [Slogan]),
+ ?SEV_ANNOUNCE_READY(Parent, Slogan).
+
+sc_rc_tcp_client_await_continue(Parent, Slogan) ->
+ ?SEV_IPRINT("await ~w continue", [Slogan]),
+ ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan).
+
+sc_rc_tcp_client_connect(Sock, ServerSA) ->
+ i("sc_rc_tcp_client_connect -> entry"),
+ case socket:connect(Sock, ServerSA) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ exit({connect, Reason})
+ end.
+
+sc_rc_tcp_client_close(Sock, Path) ->
+ i("sc_rc_tcp_client_close -> entry"),
+ case socket:close(Sock) of
+ ok ->
+ unlink_path(Path),
+ ok;
+ {error, Reason} ->
+ ?SEV_EPRINT("failed closing: "
+ "~n Reason: ~p", [Reason]),
+ unlink_path(Path),
+ {error, {close, Reason}}
+ end.
+
+sc_rc_tcp_client_await_terminate(Parent) ->
+ i("sc_rc_tcp_client_await_terminate -> entry"),
+ case ?SEV_AWAIT_TERMINATE(Parent, parent) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ Reason
+ end.
+
+
+%% The handlers run on the same node as the server (the local node).
+
+sc_rc_tcp_handler_start(ID, Recv, Sock) ->
+ Self = self(),
+ Fun = fun() -> sc_rc_tcp_handler(ID, Self, Recv, Sock) end,
+ {Pid, _} = erlang:spawn_monitor(Fun),
+ Pid.
+
+sc_rc_tcp_handler(ID, Parent, Recv, Sock) ->
+ sc_rc_tcp_handler_init(ID, socket:getopt(Sock, otp, fd), Parent),
+ sc_rc_tcp_handler_await(Parent, recv),
+ RecvRes = sc_rc_tcp_handler_recv(Recv, Sock),
+ sc_rc_tcp_handler_announce_ready(Parent, recv, RecvRes),
+ Reason = sc_rc_tcp_handler_await(Parent, terminate),
+ exit(Reason).
+
+sc_rc_tcp_handler_init(ID, {ok, FD}, Parent) ->
+ put(sname, f("handler-~w:~w", [ID, FD])),
+ _MRef = erlang:monitor(process, Parent),
+ ?SEV_IPRINT("started"),
+ ?SEV_ANNOUNCE_READY(Parent, init),
+ ok.
+
+sc_rc_tcp_handler_await(Parent, terminate) ->
+ ?SEV_IPRINT("await terminate"),
+ ?SEV_AWAIT_TERMINATE(Parent, tester);
+sc_rc_tcp_handler_await(Parent, Slogan) ->
+ ?SEV_IPRINT("await ~w", [Slogan]),
+ ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan).
+
+sc_rc_tcp_handler_recv(Recv, Sock) ->
+ ?SEV_IPRINT("recv"),
+ try Recv(Sock) of
+ {error, closed} ->
+ ok;
+ {ok, _} ->
+ ?SEV_IPRINT("unexpected success"),
+ {error, unexpected_success};
+ {error, Reason} = ERROR ->
+ ?SEV_IPRINT("receive error: "
+ "~n ~p", [Reason]),
+ ERROR
+ catch
+ C:E:S ->
+ ?SEV_IPRINT("receive failure: "
+ "~n Class: ~p"
+ "~n Error: ~p"
+ "~n Stack: ~p", [C, E, S]),
+ {error, {recv, C, E, S}}
+ end.
+
+sc_rc_tcp_handler_announce_ready(Parent, Slogan, Result) ->
+ ?SEV_IPRINT("announce ready"),
+ ?SEV_ANNOUNCE_READY(Parent, Slogan, Result),
+ ok.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% remotely closed while the process is calling the recvmsg function.
+%% Socket is IPv4.
+
+sc_rc_recvmsg_response_tcp4(suite) ->
+ [];
+sc_rc_recvmsg_response_tcp4(doc) ->
+ [];
+sc_rc_recvmsg_response_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(30)),
+ tc_try(sc_rc_recvmsg_response_tcp4,
+ fun() ->
+ Recv = fun(Sock) -> socket:recvmsg(Sock) end,
+ InitState = #{domain => inet,
+ protocol => tcp,
+ recv => Recv},
+ ok = sc_rc_receive_response_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% remotely closed while the process is calling the recvmsg function.
+%% Socket is IPv6.
+
+sc_rc_recvmsg_response_tcp6(suite) ->
+ [];
+sc_rc_recvmsg_response_tcp6(doc) ->
+ [];
+sc_rc_recvmsg_response_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(30)),
+ tc_try(sc_rc_recvmsg_response_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ Recv = fun(Sock) -> socket:recvmsg(Sock) end,
+ InitState = #{domain => inet6,
+ protocol => tcp,
+ recv => Recv},
+ ok = sc_rc_receive_response_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% remotely closed while the process is calling the recvmsg function.
+%% Socket is Unix Domain (stream) socket.
+
+sc_rc_recvmsg_response_tcpL(suite) ->
+ [];
+sc_rc_recvmsg_response_tcpL(doc) ->
+ [];
+sc_rc_recvmsg_response_tcpL(_Config) when is_list(_Config) ->
+ ?TT(?SECS(30)),
+ tc_try(sc_rc_recvmsg_response_tcpL,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ Recv = fun(Sock) -> socket:recvmsg(Sock) end,
+ InitState = #{domain => local,
+ protocol => default,
+ recv => Recv},
+ ok = sc_rc_receive_response_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% remotely closed while the process is calling the recv function.
+%% The remote client sends data, then shutdown(write) and then the
+%% reader attempts a recv.
+%% Socket is IPv4.
+%%
+%% To minimize the chance of "weirdness", we should really have test cases
+%% where the two sides of the connection is on different machines. But for
+%% now, we will make do with different VMs on the same host.
+%% This would of course not work for Unix Domain sockets.
+%%
+
+sc_rs_recv_send_shutdown_receive_tcp4(suite) ->
+ [];
+sc_rs_recv_send_shutdown_receive_tcp4(doc) ->
+ [];
+sc_rs_recv_send_shutdown_receive_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(30)),
+ tc_try(sc_rs_recv_send_shutdown_receive_tcp4,
+ fun() ->
+ MsgData = ?DATA,
+ Recv = fun(Sock) ->
+ socket:recv(Sock)
+ end,
+ Send = fun(Sock, Data) ->
+ socket:send(Sock, Data)
+ end,
+ InitState = #{domain => inet,
+ proto => tcp,
+ recv => Recv,
+ send => Send,
+ data => MsgData},
+ ok = sc_rs_send_shutdown_receive_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% remotely closed while the process is calling the recv function.
+%% The remote client sends data, then shutdown(write) and then the
+%% reader attempts a recv.
+%% Socket is IPv6.
+
+sc_rs_recv_send_shutdown_receive_tcp6(suite) ->
+ [];
+sc_rs_recv_send_shutdown_receive_tcp6(doc) ->
+ [];
+sc_rs_recv_send_shutdown_receive_tcp6(_Config) when is_list(_Config) ->
+ tc_try(sc_rs_recv_send_shutdown_receive_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ ?TT(?SECS(10)),
+ MsgData = ?DATA,
+ Recv = fun(Sock) ->
+ socket:recv(Sock)
+ end,
+ Send = fun(Sock, Data) ->
+ socket:send(Sock, Data)
+ end,
+ InitState = #{domain => inet6,
+ proto => tcp,
+ recv => Recv,
+ send => Send,
+ data => MsgData},
+ ok = sc_rs_send_shutdown_receive_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% remotely closed while the process is calling the recv function.
+%% The remote client sends data, then shutdown(write) and then the
+%% reader attempts a recv.
+%% Socket is Unix Domain (stream) socket.
+
+sc_rs_recv_send_shutdown_receive_tcpL(suite) ->
+ [];
+sc_rs_recv_send_shutdown_receive_tcpL(doc) ->
+ [];
+sc_rs_recv_send_shutdown_receive_tcpL(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(sc_rs_recv_send_shutdown_receive_tcpL,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ MsgData = ?DATA,
+ Recv = fun(Sock) ->
+ socket:recv(Sock)
+ end,
+ Send = fun(Sock, Data) ->
+ socket:send(Sock, Data)
+ end,
+ InitState = #{domain => local,
+ proto => default,
+ recv => Recv,
+ send => Send,
+ data => MsgData},
+ ok = sc_rs_send_shutdown_receive_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+sc_rs_send_shutdown_receive_tcp(InitState) ->
+ %% The connection is handled by a handler processes.
+ %% This are created (on the fly) and handled internally
+ %% by the server!
+ ServerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ i("get local address for ~p", [Domain]),
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "create listen socket",
+ cmd => fun(#{domain := Domain, proto := Proto} = State) ->
+ case socket:open(Domain, stream, Proto) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{domain := local,
+ lsock := LSock,
+ local_sa := LSA} = _State) ->
+ case socket:bind(LSock, LSA) of
+ {ok, _Port} ->
+ ok; % We do not care about the port for local
+ {error, _} = ERROR ->
+ ERROR
+ end;
+ (#{lsock := LSock, local_sa := LSA} = State) ->
+ case sock_bind(LSock, LSA) of
+ {ok, Port} ->
+ ?SEV_IPRINT("bound to port: ~w", [Port]),
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{domain := local,
+ tester := Tester, local_sa := LSA}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, LSA),
+ ok;
+ (#{tester := Tester, local_sa := LSA, lport := Port}) ->
+ ServerSA = LSA#{port => Port},
+ ?SEV_ANNOUNCE_READY(Tester, init, ServerSA),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "accept",
+ cmd => fun(#{lsock := LSock, recv := Recv} = State) ->
+ case socket:accept(LSock) of
+ {ok, Sock} ->
+ ?SEV_IPRINT("accepted: try start handler"),
+ Handler =
+ sc_rs_tcp_handler_start(Recv, Sock),
+ ?SEV_IPRINT("handler started"),
+ {ok, State#{csock => Sock,
+ handler => Handler}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "await handler ready (init)",
+ cmd => fun(#{tester := Tester,
+ handler := Handler} = _State) ->
+ ?SEV_AWAIT_READY(Handler, handler, init,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept),
+ ok
+ end},
+
+ #{desc => "await continue (first recv)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv)
+ end},
+ #{desc => "order handler to receive (first)",
+ cmd => fun(#{handler := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ #{desc => "await ready from handler (first recv)",
+ cmd => fun(#{tester := Tester, handler := Pid} = _State) ->
+ case ?SEV_AWAIT_READY(Pid, handler, recv,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ ?SEV_IPRINT("first recv: ~p", [Result]),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (first recv)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv),
+ ok
+ end},
+ #{desc => "await continue (second recv)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv)
+ end},
+ #{desc => "order handler to receive (second)",
+ cmd => fun(#{handler := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ #{desc => "await ready from handler (second recv)",
+ cmd => fun(#{tester := Tester, handler := Pid} = _State) ->
+ case ?SEV_AWAIT_READY(Pid, handler, recv,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ ?SEV_IPRINT("second recv: ~p", [Result]),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (second recv)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv),
+ ok
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order handler to terminate",
+ cmd => fun(#{handler := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await handler termination",
+ cmd => fun(#{handler := Pid} = State) ->
+ ?SEV_AWAIT_TERMINATION(Pid),
+ State1 = maps:remove(csock, State),
+ State2 = maps:remove(handler, State1),
+ {ok, State2}
+ end},
+ #{desc => "close listen socket",
+ cmd => fun(#{domain := local,
+ lsock := Sock,
+ local_sa := #{path := Path}} = State) ->
+ socket:close(Sock),
+ State1 =
+ unlink_path(Path,
+ fun() ->
+ maps:remove(local_sa, State)
+ end,
+ fun() -> State end),
+ {ok, maps:remove(lsock, State1)};
+ (#{lsock := LSock} = State) ->
+ case socket:close(LSock) of
+ ok ->
+ {ok, maps:remove(lsock, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ ClientSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ {Tester, ServerSA} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ server_sa => ServerSA}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "create node",
+ cmd => fun(#{host := Host} = State) ->
+ case start_node(Host, client) of
+ {ok, Node} ->
+ ?SEV_IPRINT("client node ~p started",
+ [Node]),
+ {ok, State#{node => Node}};
+ {error, Reason} ->
+ {skip, Reason}
+ end
+ end},
+ #{desc => "monitor client node",
+ cmd => fun(#{node := Node} = _State) ->
+ true = erlang:monitor_node(Node, true),
+ ok
+ end},
+ #{desc => "start remote client on client node",
+ cmd => fun(#{node := Node,
+ send := Send} = State) ->
+ Pid = sc_rs_tcp_client_start(Node, Send),
+ ?SEV_IPRINT("client ~p started", [Pid]),
+ {ok, State#{rclient => Pid}}
+ end},
+ #{desc => "monitor remote client",
+ cmd => fun(#{rclient := Pid}) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order remote client to start",
+ cmd => fun(#{rclient := Client,
+ proto := Proto,
+ server_sa := ServerSA}) ->
+ ?SEV_ANNOUNCE_START(Client, {ServerSA, Proto}),
+ ok
+ end},
+ #{desc => "await remote client ready",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, rclient, init,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (connect)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, connect,
+ [{rclient, Client}]),
+ ok
+ end},
+ #{desc => "order remote client to continue (connect)",
+ cmd => fun(#{rclient := Client}) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, connect),
+ ok
+ end},
+ #{desc => "await client process ready (connect)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, rclient, connect,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (connect)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, connect),
+ ok
+ end},
+
+ #{desc => "await continue (send)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = State) ->
+ case ?SEV_AWAIT_CONTINUE(Tester, tester, send,
+ [{rclient, Client}]) of
+ {ok, Data} ->
+ {ok, State#{rclient_data => Data}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order remote client to send",
+ cmd => fun(#{rclient := Client,
+ rclient_data := Data}) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send, Data),
+ ok
+ end},
+ #{desc => "await remote client ready (closed)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, rclient, send,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (send)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send),
+ ok
+ end},
+
+
+ #{desc => "await continue (shutdown)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, shutdown,
+ [{rclient, Client}]),
+ ok
+ end},
+ #{desc => "order remote client to shutdown",
+ cmd => fun(#{rclient := Client}) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, shutdown),
+ ok
+ end},
+ #{desc => "await remote client ready (shiutdown)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, rclient, shutdown,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (shutdown)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, shutdown),
+ ok
+ end},
+
+ #{desc => "await continue (close)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, close,
+ [{rclient, Client}]),
+ ok
+ end},
+ #{desc => "order remote client to close",
+ cmd => fun(#{rclient := Client}) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, close),
+ ok
+ end},
+ #{desc => "await remote client ready (closed)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, rclient, close,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (close)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, close),
+ ok
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester,
+ [{rclient, Client}]) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "kill remote client",
+ cmd => fun(#{rclient := Client}) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await remote client termination",
+ cmd => fun(#{rclient := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(rclient, State),
+ {ok, State1}
+ end},
+ #{desc => "stop client node",
+ cmd => fun(#{node := Node} = _State) ->
+ stop_node(Node)
+ end},
+ #{desc => "await client node termination",
+ cmd => fun(#{node := Node} = State) ->
+ receive
+ {nodedown, Node} ->
+ State1 = maps:remove(node_id, State),
+ State2 = maps:remove(node, State1),
+ {ok, State2}
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = State) ->
+ {ok, ServerSA} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{server_sa => ServerSA}}
+ end},
+
+ %% Start the client(s)
+ #{desc => "order client start",
+ cmd => fun(#{client := Pid,
+ server_sa := ServerSA} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, ServerSA),
+ ok
+ end},
+ #{desc => "await client ready (init)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client, init)
+ end},
+
+ %% The actual test
+ #{desc => "order server continue (accept)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, accept),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order client continue (connect)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, connect),
+ ok
+ end},
+ #{desc => "await client ready (connect)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, connect,
+ [{server, Server}]),
+ ok
+ end},
+ #{desc => "await server ready (accept)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, accept,
+ [{client, Client}]),
+ ok
+ end},
+
+ #{desc => "order client continue (send)",
+ cmd => fun(#{client := Pid,
+ data := Data} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, send, Data),
+ ok
+ end},
+ #{desc => "await client ready (send)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, send,
+ [{server, Server}]),
+ ok
+ end},
+
+ #{desc => "order client continue (shutdown)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, shutdown),
+ ok
+ end},
+ #{desc => "await client ready (shutdown)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, shutdown,
+ [{server, Server}]),
+ ok
+ end},
+
+ #{desc => "order server continue (first recv)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ #{desc => "await server ready (first recv)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv,
+ [{client, Client}]),
+ ok
+ end},
+
+ #{desc => "order server continue (second recv)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ #{desc => "await server ready (second recv)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv,
+ [{client, Client}]),
+ ok
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "order client continue (close)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, close),
+ ok
+ end},
+ #{desc => "await client ready (close)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, close,
+ [{server, Server}]),
+ ok
+ end},
+
+ %% Terminations
+ #{desc => "order client to terminate",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await client termination",
+ cmd => fun(#{client := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(client, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(server, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start server evaluator"),
+ ServerInitState = #{domain => maps:get(domain, InitState),
+ proto => maps:get(proto, InitState),
+ recv => maps:get(recv, InitState)},
+ Server = ?SEV_START("server", ServerSeq, ServerInitState),
+
+ i("start client evaluator"),
+ ClientInitState = #{host => local_host(),
+ domain => maps:get(domain, InitState),
+ proto => maps:get(proto, InitState),
+ send => maps:get(send, InitState)},
+ Client = ?SEV_START("client", ClientSeq, ClientInitState),
+
+ i("start 'tester' evaluator"),
+ TesterInitState = #{server => Server#ev.pid,
+ client => Client#ev.pid,
+ data => maps:get(data, InitState)},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]).
+
+
+sc_rs_tcp_client_start(Node, Send) ->
+ Self = self(),
+ Fun = fun() -> sc_rs_tcp_client(Self, Send) end,
+ erlang:spawn(Node, Fun).
+
+
+sc_rs_tcp_client(Parent, Send) ->
+ sc_rs_tcp_client_init(Parent),
+ {ServerSA, Proto} = sc_rs_tcp_client_await_start(Parent),
+ Domain = maps:get(family, ServerSA),
+ Sock = sc_rs_tcp_client_create(Domain, Proto),
+ Path = sc_rs_tcp_client_bind(Sock, Domain),
+ sc_rs_tcp_client_announce_ready(Parent, init),
+ sc_rs_tcp_client_await_continue(Parent, connect),
+ sc_rs_tcp_client_connect(Sock, ServerSA),
+ sc_rs_tcp_client_announce_ready(Parent, connect),
+ Data = sc_rs_tcp_client_await_continue(Parent, send),
+ sc_rs_tcp_client_send(Sock, Send, Data),
+ sc_rs_tcp_client_announce_ready(Parent, send),
+ sc_rs_tcp_client_await_continue(Parent, shutdown),
+ sc_rs_tcp_client_shutdown(Sock),
+ sc_rs_tcp_client_announce_ready(Parent, shutdown),
+ sc_rs_tcp_client_await_continue(Parent, close),
+ sc_rs_tcp_client_close(Sock, Path),
+ sc_rs_tcp_client_announce_ready(Parent, close),
+ Reason = sc_rs_tcp_client_await_terminate(Parent),
+ ?SEV_IPRINT("terminate"),
+ exit(Reason).
+
+sc_rs_tcp_client_init(Parent) ->
+ put(sname, "rclient"),
+ ?SEV_IPRINT("init"),
+ _MRef = erlang:monitor(process, Parent),
+ ok.
+
+sc_rs_tcp_client_await_start(Parent) ->
+ i("sc_rs_tcp_client_await_start -> entry"),
+ ?SEV_AWAIT_START(Parent).
+
+sc_rs_tcp_client_create(Domain, Proto) ->
+ i("sc_rs_tcp_client_create -> entry"),
+ case socket:open(Domain, stream, Proto) of
+ {ok, Sock} ->
+ Sock;
+ {error, Reason} ->
+ exit({open_failed, Reason})
+ end.
+
+sc_rs_tcp_client_bind(Sock, Domain) ->
+ i("sc_rs_tcp_client_bind -> entry"),
+ LSA = which_local_socket_addr(Domain),
+ case socket:bind(Sock, LSA) of
+ {ok, _} ->
+ case socket:sockname(Sock) of
+ {ok, #{family := local, path := Path}} ->
+ Path;
+ {ok, _} ->
+ undefined;
+ {error, Reason1} ->
+ exit({sockname, Reason1})
+ end;
+ {error, Reason} ->
+ exit({bind, Reason})
+ end.
+
+sc_rs_tcp_client_announce_ready(Parent, Slogan) ->
+ ?SEV_ANNOUNCE_READY(Parent, Slogan).
+
+sc_rs_tcp_client_await_continue(Parent, Slogan) ->
+ i("sc_rs_tcp_client_await_continue -> entry"),
+ case ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan) of
+ ok ->
+ ok;
+ {ok, Extra} ->
+ Extra;
+ {error, Reason} ->
+ exit({await_continue, Slogan, Reason})
+ end.
+
+
+sc_rs_tcp_client_connect(Sock, ServerSA) ->
+ i("sc_rs_tcp_client_connect -> entry"),
+ case socket:connect(Sock, ServerSA) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ exit({connect, Reason})
+ end.
+
+sc_rs_tcp_client_send(Sock, Send, Data) ->
+ i("sc_rs_tcp_client_send -> entry"),
+ case Send(Sock, Data) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ exit({send, Reason})
+ end.
+
+sc_rs_tcp_client_shutdown(Sock) ->
+ i("sc_rs_tcp_client_shutdown -> entry"),
+ case socket:shutdown(Sock, write) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ exit({shutdown, Reason})
+ end.
+
+sc_rs_tcp_client_close(Sock, Path) ->
+ i("sc_rs_tcp_client_close -> entry"),
+ case socket:close(Sock) of
+ ok ->
+ unlink_path(Path),
+ ok;
+ {error, Reason} ->
+ ?SEV_EPRINT("failed closing: "
+ "~n Reason: ~p", [Reason]),
+ unlink_path(Path),
+ {error, {close, Reason}}
+ end.
+
+sc_rs_tcp_client_await_terminate(Parent) ->
+ i("sc_rs_tcp_client_await_terminate -> entry"),
+ case ?SEV_AWAIT_TERMINATE(Parent, parent) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ Reason
+ end.
+
+
+%% The handlers run on the same node as the server (the local node).
+
+sc_rs_tcp_handler_start(Recv, Sock) ->
+ Self = self(),
+ Fun = fun() -> sc_rs_tcp_handler(Self, Recv, Sock) end,
+ {Pid, _} = erlang:spawn_monitor(Fun),
+ Pid.
+
+sc_rs_tcp_handler(Parent, Recv, Sock) ->
+ sc_rs_tcp_handler_init(Parent),
+ sc_rs_tcp_handler_await(Parent, recv),
+ ok = sc_rs_tcp_handler_recv(Recv, Sock, true),
+ sc_rs_tcp_handler_announce_ready(Parent, recv, received),
+ sc_rs_tcp_handler_await(Parent, recv),
+ ok = sc_rs_tcp_handler_recv(Recv, Sock, false),
+ sc_rs_tcp_handler_announce_ready(Parent, recv, closed),
+ Reason = sc_rs_tcp_handler_await(Parent, terminate),
+ exit(Reason).
+
+sc_rs_tcp_handler_init(Parent) ->
+ put(sname, "handler"),
+ _MRef = erlang:monitor(process, Parent),
+ ?SEV_IPRINT("started"),
+ ?SEV_ANNOUNCE_READY(Parent, init),
+ ok.
+
+sc_rs_tcp_handler_await(Parent, terminate) ->
+ ?SEV_IPRINT("await terminate"),
+ ?SEV_AWAIT_TERMINATE(Parent, tester);
+sc_rs_tcp_handler_await(Parent, Slogan) ->
+ ?SEV_IPRINT("await ~w", [Slogan]),
+ ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan).
+
+%% This hould actually work - we leave it for now
+sc_rs_tcp_handler_recv(Recv, Sock, First) ->
+ ?SEV_IPRINT("recv"),
+ try Recv(Sock) of
+ {ok, _} when (First =:= true) ->
+ ok;
+ {error, closed} when (First =:= false) ->
+ ok;
+ {ok, _} ->
+ ?SEV_IPRINT("unexpected success"),
+ {error, unexpected_success};
+ {error, Reason} = ERROR ->
+ ?SEV_IPRINT("receive error: "
+ "~n ~p", [Reason]),
+ ERROR
+ catch
+ C:E:S ->
+ ?SEV_IPRINT("receive failure: "
+ "~n Class: ~p"
+ "~n Error: ~p"
+ "~n Stack: ~p", [C, E, S]),
+ {error, {recv, C, E, S}}
+ end.
+
+sc_rs_tcp_handler_announce_ready(Parent, Slogan, Result) ->
+ ?SEV_IPRINT("announce ready"),
+ ?SEV_ANNOUNCE_READY(Parent, Slogan, Result),
+ ok.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% remotely closed while the process is calling the recvmsg function.
+%% The remote client sends data, then shutdown(write) and then the
+%% reader attempts a recv.
+%% Socket is IPv4.
+
+sc_rs_recvmsg_send_shutdown_receive_tcp4(suite) ->
+ [];
+sc_rs_recvmsg_send_shutdown_receive_tcp4(doc) ->
+ [];
+sc_rs_recvmsg_send_shutdown_receive_tcp4(_Config) when is_list(_Config) ->
+ tc_try(sc_rs_recvmsg_send_shutdown_receive_tcp4,
+ fun() ->
+ ?TT(?SECS(30)),
+ MsgData = ?DATA,
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock) of
+ {ok, #{addr := undefined,
+ iov := [Data]}} ->
+ {ok, Data};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ Send = fun(Sock, Data) when is_binary(Data) ->
+ MsgHdr = #{iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ InitState = #{domain => inet,
+ proto => tcp,
+ recv => Recv,
+ send => Send,
+ data => MsgData},
+ ok = sc_rs_send_shutdown_receive_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% remotely closed while the process is calling the recvmsg function.
+%% The remote client sends data, then shutdown(write) and then the
+%% reader attempts a recv.
+%% Socket is IPv6.
+
+sc_rs_recvmsg_send_shutdown_receive_tcp6(suite) ->
+ [];
+sc_rs_recvmsg_send_shutdown_receive_tcp6(doc) ->
+ [];
+sc_rs_recvmsg_send_shutdown_receive_tcp6(_Config) when is_list(_Config) ->
+ tc_try(sc_rs_recvmsg_send_shutdown_receive_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ ?TT(?SECS(10)),
+ MsgData = ?DATA,
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock) of
+ {ok, #{addr := undefined,
+ iov := [Data]}} ->
+ {ok, Data};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ Send = fun(Sock, Data) when is_binary(Data) ->
+ MsgHdr = #{iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ InitState = #{domain => inet6,
+ proto => tcp,
+ recv => Recv,
+ send => Send,
+ data => MsgData},
+ ok = sc_rs_send_shutdown_receive_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% remotely closed while the process is calling the recvmsg function.
+%% The remote client sends data, then shutdown(write) and then the
+%% reader attempts a recv.
+%% Socket is UNix Domain (stream) socket.
+
+sc_rs_recvmsg_send_shutdown_receive_tcpL(suite) ->
+ [];
+sc_rs_recvmsg_send_shutdown_receive_tcpL(doc) ->
+ [];
+sc_rs_recvmsg_send_shutdown_receive_tcpL(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(sc_rs_recvmsg_send_shutdown_receive_tcpL,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ {ok, CWD} = file:get_cwd(),
+ ?SEV_IPRINT("CWD: ~s", [CWD]),
+ MsgData = ?DATA,
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock) of
+ %% On some platforms, the address
+ %% is *not* provided (e.g. FreeBSD)
+ {ok, #{addr := undefined,
+ iov := [Data]}} ->
+ {ok, Data};
+ %% On some platforms, the address
+ %% *is* provided (e.g. linux)
+ {ok, #{addr := #{family := local},
+ iov := [Data]}} ->
+ {ok, Data};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ Send = fun(Sock, Data) when is_binary(Data) ->
+ MsgHdr = #{iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ InitState = #{domain => local,
+ proto => default,
+ recv => Recv,
+ send => Send,
+ data => MsgData},
+ ok = sc_rs_send_shutdown_receive_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to (simply) test that the counters
+%% for both read and write.
+%% So that its easy to extend, we use fun's for read and write.
+%% We use TCP on IPv4.
+
+traffic_send_and_recv_counters_tcp4(suite) ->
+ [];
+traffic_send_and_recv_counters_tcp4(doc) ->
+ [];
+traffic_send_and_recv_counters_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(15)),
+ tc_try(traffic_send_and_recv_counters_tcp4,
+ fun() ->
+ InitState = #{domain => inet,
+ proto => tcp,
+ recv => fun(S) -> socket:recv(S) end,
+ send => fun(S, D) -> socket:send(S, D) end},
+ ok = traffic_send_and_recv_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to (simply) test that the counters
+%% for both read and write.
+%% So that its easy to extend, we use fun's for read and write.
+%% We use TCP on IPv6.
+
+traffic_send_and_recv_counters_tcp6(suite) ->
+ [];
+traffic_send_and_recv_counters_tcp6(doc) ->
+ [];
+traffic_send_and_recv_counters_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(15)),
+ tc_try(traffic_send_and_recv_counters_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet6,
+ proto => tcp,
+ recv => fun(S) -> socket:recv(S) end,
+ send => fun(S, D) -> socket:send(S, D) end},
+ ok = traffic_send_and_recv_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to (simply) test that the counters
+%% for both read and write.
+%% So that its easy to extend, we use fun's for read and write.
+%% We use default (TCP) on local.
+
+traffic_send_and_recv_counters_tcpL(suite) ->
+ [];
+traffic_send_and_recv_counters_tcpL(doc) ->
+ [];
+traffic_send_and_recv_counters_tcpL(_Config) when is_list(_Config) ->
+ ?TT(?SECS(15)),
+ tc_try(traffic_send_and_recv_counters_tcpL,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ InitState = #{domain => local,
+ proto => default,
+ recv => fun(S) -> socket:recv(S) end,
+ send => fun(S, D) -> socket:send(S, D) end},
+ ok = traffic_send_and_recv_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to (simply) test that the counters
+%% for both read and write.
+%% So that its easy to extend, we use fun's for read and write.
+%% We use TCP on IPv4.
+
+traffic_sendmsg_and_recvmsg_counters_tcp4(suite) ->
+ [];
+traffic_sendmsg_and_recvmsg_counters_tcp4(doc) ->
+ [];
+traffic_sendmsg_and_recvmsg_counters_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(15)),
+ tc_try(traffic_sendmsg_and_recvmsg_counters_tcp4,
+ fun() ->
+ InitState = #{domain => inet,
+ proto => tcp,
+ recv => fun(S) ->
+ case socket:recvmsg(S) of
+ {ok, #{addr := _Source,
+ iov := [Data]}} ->
+ {ok, Data};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ send => fun(S, Data) ->
+ MsgHdr = #{iov => [Data]},
+ socket:sendmsg(S, MsgHdr)
+ end},
+ ok = traffic_send_and_recv_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to (simply) test that the counters
+%% for both read and write.
+%% So that its easy to extend, we use fun's for read and write.
+%% We use TCP on IPv6.
+
+traffic_sendmsg_and_recvmsg_counters_tcp6(suite) ->
+ [];
+traffic_sendmsg_and_recvmsg_counters_tcp6(doc) ->
+ [];
+traffic_sendmsg_and_recvmsg_counters_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(15)),
+ tc_try(traffic_sendmsg_and_recvmsg_counters_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet6,
+ proto => tcp,
+ recv => fun(S) ->
+ case socket:recvmsg(S) of
+ {ok, #{addr := _Source,
+ iov := [Data]}} ->
+ {ok, Data};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ send => fun(S, Data) ->
+ MsgHdr = #{iov => [Data]},
+ socket:sendmsg(S, MsgHdr)
+ end},
+ ok = traffic_send_and_recv_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to (simply) test that the counters
+%% for both read and write.
+%% So that its easy to extend, we use fun's for read and write.
+%% We use default (TCP) on local.
+
+traffic_sendmsg_and_recvmsg_counters_tcpL(suite) ->
+ [];
+traffic_sendmsg_and_recvmsg_counters_tcpL(doc) ->
+ [];
+traffic_sendmsg_and_recvmsg_counters_tcpL(_Config) when is_list(_Config) ->
+ ?TT(?SECS(15)),
+ tc_try(traffic_sendmsg_and_recvmsg_counters_tcpL,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ InitState = #{domain => local,
+ proto => default,
+ recv => fun(S) ->
+ case socket:recvmsg(S) of
+ {ok, #{addr := _Source,
+ iov := [Data]}} ->
+ {ok, Data};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ send => fun(S, Data) ->
+ MsgHdr = #{iov => [Data]},
+ socket:sendmsg(S, MsgHdr)
+ end},
+ ok = traffic_send_and_recv_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+traffic_send_and_recv_tcp(InitState) ->
+ ServerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "create listen socket",
+ cmd => fun(#{domain := Domain, proto := Proto} = State) ->
+ case socket:open(Domain, stream, Proto) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, eafnosupport = Reason} ->
+ {skip, Reason};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{domain := local,
+ lsock := LSock,
+ local_sa := LSA} = _State) ->
+ case socket:bind(LSock, LSA) of
+ {ok, _Port} ->
+ ok; % We do not care about the port for local
+ {error, _} = ERROR ->
+ ERROR
+ end;
+ (#{lsock := LSock,
+ local_sa := LSA} = State) ->
+ case sock_bind(LSock, LSA) of
+ {ok, Port} ->
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock)
+ end},
+ #{desc => "initial (listen socket) counter validation (=zero)",
+ cmd => fun(#{lsock := LSock} = _State) ->
+ try socket:info(LSock) of
+ #{counters := Counters} ->
+ ?SEV_IPRINT("Validate initial listen socket counters: "
+ "~s", [format_counters(listen,
+ Counters)]),
+ traffic_sar_counters_validation(Counters)
+ catch
+ C:E:S ->
+ ?SEV_EPRINT("Failed get socket info: "
+ "~n Class: ~p"
+ "~n Error: ~p"
+ "~n Stack: ~p", [C, E, S]),
+ {error, {socket_info_failed, {C, E, S}}}
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{domain := local,
+ tester := Tester,
+ local_sa := #{path := Path}}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Path),
+ ok;
+ (#{tester := Tester,
+ lport := Port}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Port),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "accept",
+ cmd => fun(#{lsock := LSock} = State) ->
+ case socket:accept(LSock) of
+ {ok, CSock} ->
+ #{counters := LCnts} = socket:info(LSock),
+ ?SEV_IPRINT("Validate listen counters: "
+ "~s", [format_counters(listen,
+ LCnts)]),
+ traffic_sar_counters_validation(
+ LCnts,
+ [{acc_success, 1},
+ {acc_fails, 0},
+ {acc_tries, 1},
+ {acc_waits, 0}]),
+ #{counters := CCnts} = socket:info(CSock),
+ ?SEV_IPRINT("Validate initial accept counters: "
+ "~s", [format_counters(CCnts)]),
+ traffic_sar_counters_validation(CCnts),
+ {ok, State#{csock => CSock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "initial counter validation (=zero)",
+ cmd => fun(#{csock := Sock} = _State) ->
+ try socket:info(Sock) of
+ #{counters := Counters} ->
+ ?SEV_IPRINT("Validate initial counters: "
+ "~s", [format_counters(Counters)]),
+ traffic_sar_counters_validation(Counters)
+ catch
+ C:E:S ->
+ ?SEV_EPRINT("Failed get socket info: "
+ "~n Class: ~p"
+ "~n Error: ~p"
+ "~n Stack: ~p", [C, E, S]),
+ {error, {socket_info_failed, {C, E, S}}}
+ end
+ end},
+ #{desc => "announce ready (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept),
+ ok
+ end},
+
+ #{desc => "await continue (recv_and_validate 1)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv_and_validate)
+ end},
+ #{desc => "recv (1)",
+ cmd => fun(#{csock := Sock,
+ recv := Recv} = State) ->
+ case Recv(Sock) of
+ {ok, Data} ->
+ ?SEV_IPRINT("recv ~p bytes", [size(Data)]),
+ {ok, State#{read_pkg => 1,
+ read_byte => size(Data)}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "validate (recv 1)",
+ cmd => fun(#{csock := Sock,
+ read_pkg := Pkg,
+ read_byte := Byte} = _State) ->
+ try socket:info(Sock) of
+ #{counters := Counters} ->
+ ?SEV_IPRINT("validate counters: "
+ "~s", [format_counters(Counters)]),
+ traffic_sar_counters_validation(
+ Counters,
+ [{read_pkg, Pkg},
+ {read_byte, Byte},
+ {read_tries, any},
+ {read_pkg_max, any}])
+ catch
+ C:E:S ->
+ ?SEV_EPRINT("Failed get socket info: "
+ "~n Class: ~p"
+ "~n Error: ~p"
+ "~n Stack: ~p", [C, E, S]),
+ {error, {socket_info_failed, {C, E, S}}}
+ end
+ end},
+ #{desc => "announce ready (recv_and_validate 1)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_and_validate),
+ ok
+ end},
+
+ #{desc => "await continue (send_and_validate 1)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_and_validate)
+ end},
+ #{desc => "send (1)",
+ cmd => fun(#{csock := Sock,
+ send := Send} = State) ->
+ Data = ?DATA,
+ case Send(Sock, Data) of
+ ok ->
+ ?SEV_IPRINT("sent ~p bytes", [size(Data)]),
+ {ok, State#{write_pkg => 1,
+ write_byte => size(Data)}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "validate (send 1)",
+ cmd => fun(#{csock := Sock,
+ read_pkg := RPkg,
+ read_byte := RByte,
+ write_pkg := SPkg,
+ write_byte := SByte} = _State) ->
+ try socket:info(Sock) of
+ #{counters := Counters} ->
+ ?SEV_IPRINT("validate counters: "
+ "~s", [format_counters(Counters)]),
+ traffic_sar_counters_validation(
+ Counters,
+ [{read_pkg, RPkg},
+ {read_byte, RByte},
+ {write_pkg, SPkg},
+ {write_byte, SByte},
+ {read_tries, any},
+ {write_tries, any},
+ {read_pkg_max, any},
+ {write_pkg_max, any}])
+ catch
+ C:E:S ->
+ ?SEV_EPRINT("Failed get socket info: "
+ "~n Class: ~p"
+ "~n Error: ~p"
+ "~n Stack: ~p", [C, E, S]),
+ {error, {socket_info_failed, {C, E, S}}}
+ end
+ end},
+ #{desc => "announce ready (send_and_validate 1)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_and_validate),
+ ok
+ end},
+
+ #{desc => "await continue (recv_and_validate 2)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv_and_validate)
+ end},
+ #{desc => "recv (2)",
+ cmd => fun(#{csock := Sock,
+ recv := Recv,
+ read_pkg := Pkg,
+ read_byte := Byte} = State) ->
+ case Recv(Sock) of
+ {ok, Data} ->
+ ?SEV_IPRINT("recv ~p bytes", [size(Data)]),
+ {ok, State#{read_pkg => Pkg + 1,
+ read_byte => Byte + size(Data)}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "validate (recv 2)",
+ cmd => fun(#{csock := Sock,
+ read_pkg := RPkg,
+ read_byte := RByte,
+ write_pkg := SPkg,
+ write_byte := SByte} = _State) ->
+ try socket:info(Sock) of
+ #{counters := Counters} ->
+ ?SEV_IPRINT("validate counters: "
+ "~s", [format_counters(Counters)]),
+ traffic_sar_counters_validation(
+ Counters,
+ [{read_pkg, RPkg},
+ {read_byte, RByte},
+ {write_pkg, SPkg},
+ {write_byte, SByte},
+ {read_tries, any},
+ {write_tries, any},
+ {read_pkg_max, any},
+ {write_pkg_max, any}])
+ catch
+ C:E:S ->
+ ?SEV_EPRINT("Failed get socket info: "
+ "~n Class: ~p"
+ "~n Error: ~p"
+ "~n Stack: ~p", [C, E, S]),
+ {error, {socket_info_failed, {C, E, S}}}
+ end
+ end},
+ #{desc => "announce ready (recv_and_validate 2)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_and_validate),
+ ok
+ end},
+
+ #{desc => "await continue (send_and_validate 2)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_and_validate)
+ end},
+ #{desc => "send (2)",
+ cmd => fun(#{csock := Sock,
+ send := Send,
+ write_pkg := Pkg,
+ write_byte := Byte} = State) ->
+ Data = ?DATA,
+ case Send(Sock, Data) of
+ ok ->
+ ?SEV_IPRINT("sent ~p bytes", [size(Data)]),
+ {ok, State#{write_pkg => Pkg + 1,
+ write_byte => Byte + size(Data)}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "validate (send 2)",
+ cmd => fun(#{csock := Sock,
+ read_pkg := RPkg,
+ read_byte := RByte,
+ write_pkg := SPkg,
+ write_byte := SByte} = _State) ->
+ try socket:info(Sock) of
+ #{counters := Counters} ->
+ ?SEV_IPRINT("validate counters: "
+ "~s", [format_counters(Counters)]),
+ traffic_sar_counters_validation(
+ Counters,
+ [{read_pkg, RPkg},
+ {read_byte, RByte},
+ {write_pkg, SPkg},
+ {write_byte, SByte},
+ {read_tries, any},
+ {write_tries, any},
+ {read_pkg_max, any},
+ {write_pkg_max, any}])
+ catch
+ C:E:S ->
+ ?SEV_EPRINT("Failed get socket info: "
+ "~n Class: ~p"
+ "~n Error: ~p"
+ "~n Stack: ~p", [C, E, S]),
+ {error, {socket_info_failed, {C, E, S}}}
+ end
+ end},
+ #{desc => "announce ready (send_and_validate 2)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_and_validate),
+ ok
+ end},
+
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close connection socket (just in case)",
+ cmd => fun(#{csock := Sock} = State) ->
+ (catch socket:close(Sock)),
+ {ok, maps:remove(csock, State)}
+ end},
+ #{desc => "close listen socket",
+ cmd => fun(#{domain := local,
+ lsock := Sock,
+ local_sa := #{path := Path}} = State) ->
+ ok = socket:close(Sock),
+ State1 =
+ unlink_path(Path,
+ fun() ->
+ maps:remove(local_sa, State)
+ end,
+ fun() -> State end),
+ {ok, maps:remove(lsock, State1)};
+ (#{lsock := Sock} = State) ->
+ (catch socket:close(Sock)),
+ {ok, maps:remove(lsock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ ClientSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start (from tester)",
+ cmd => fun(#{domain := local} = State) ->
+ {Tester, Path} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, server_path => Path}};
+ (State) ->
+ {Tester, Port} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, server_port => Port}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which server (local) address",
+ cmd => fun(#{domain := local = Domain,
+ server_path := Path} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ SSA = #{family => Domain, path => Path},
+ {ok, State#{local_sa => LSA, server_sa => SSA}};
+ (#{domain := Domain, server_port := Port} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ SSA = LSA#{port => Port},
+ {ok, State#{local_sa => LSA, server_sa => SSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ case socket:open(Domain, stream, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, eafnosupport = Reason} ->
+ {skip, Reason};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{sock := Sock, local_sa := LSA} = _State) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (connect)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, connect),
+ ok
+ end},
+ #{desc => "connect to server",
+ cmd => fun(#{sock := Sock, server_sa := SSA}) ->
+ socket:connect(Sock, SSA)
+ end},
+ #{desc => "announce ready (connect)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, connect),
+ ok
+ end},
+
+ #{desc => "await continue (send_and_validate 1)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_and_validate)
+ end},
+ #{desc => "send (1)",
+ cmd => fun(#{sock := Sock,
+ send := Send} = State) ->
+ Data = ?DATA,
+ case Send(Sock, Data) of
+ ok ->
+ ?SEV_IPRINT("sent ~p bytes", [size(Data)]),
+ {ok, State#{write_pkg => 1,
+ write_byte => size(Data)}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "validate (send 1)",
+ cmd => fun(#{sock := Sock,
+ write_pkg := SPkg,
+ write_byte := SByte} = _State) ->
+ try socket:info(Sock) of
+ #{counters := Counters} ->
+ ?SEV_IPRINT("validate counters: "
+ "~s", [format_counters(Counters)]),
+ traffic_sar_counters_validation(
+ Counters,
+ [{write_pkg, SPkg},
+ {write_byte, SByte},
+ {write_tries, any},
+ {write_pkg_max, any}])
+ catch
+ C:E:S ->
+ ?SEV_EPRINT("Failed get socket info: "
+ "~n Class: ~p"
+ "~n Error: ~p"
+ "~n Stack: ~p", [C, E, S]),
+ {error, {socket_info_failed, {C, E, S}}}
+ end
+ end},
+ #{desc => "announce ready (send_and_validate 1)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_and_validate),
+ ok
+ end},
+
+ #{desc => "await continue (recv_and_validate 1)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv_and_validate)
+ end},
+ #{desc => "recv (1)",
+ cmd => fun(#{sock := Sock,
+ recv := Recv} = State) ->
+ case Recv(Sock) of
+ {ok, Data} ->
+ ?SEV_IPRINT("recv ~p bytes", [size(Data)]),
+ {ok, State#{read_pkg => 1,
+ read_byte => size(Data)}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "validate (recv 1)",
+ cmd => fun(#{sock := Sock,
+ read_pkg := RPkg,
+ read_byte := RByte,
+ write_pkg := SPkg,
+ write_byte := SByte} = _State) ->
+ try socket:info(Sock) of
+ #{counters := Counters} ->
+ ?SEV_IPRINT("validate counters: "
+ "~s", [format_counters(Counters)]),
+ traffic_sar_counters_validation(
+ Counters,
+ [{read_pkg, RPkg},
+ {read_byte, RByte},
+ {write_pkg, SPkg},
+ {write_byte, SByte},
+ {read_tries, any},
+ {write_tries, any},
+ {read_pkg_max, any},
+ {write_pkg_max, any}])
+ catch
+ C:E:S ->
+ ?SEV_EPRINT("Failed get socket info: "
+ "~n Class: ~p"
+ "~n Error: ~p"
+ "~n Stack: ~p", [C, E, S]),
+ {error, {socket_info_failed, {C, E, S}}}
+ end
+ end},
+ #{desc => "announce ready (recv_and_validate 1)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_and_validate),
+ ok
+ end},
+
+ #{desc => "await continue (send_and_validate 2)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_and_validate)
+ end},
+ #{desc => "send (2)",
+ cmd => fun(#{sock := Sock,
+ send := Send,
+ write_pkg := SPkg,
+ write_byte := SByte} = State) ->
+ Data = ?DATA,
+ case Send(Sock, Data) of
+ ok ->
+ ?SEV_IPRINT("sent ~p bytes", [size(Data)]),
+ {ok, State#{write_pkg => SPkg + 1,
+ write_byte => SByte + size(Data)}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "validate (send 2)",
+ cmd => fun(#{sock := Sock,
+ read_pkg := RPkg,
+ read_byte := RByte,
+ write_pkg := SPkg,
+ write_byte := SByte} = _State) ->
+ try socket:info(Sock) of
+ #{counters := Counters} ->
+ ?SEV_IPRINT("validate counters: "
+ "~s", [format_counters(Counters)]),
+ traffic_sar_counters_validation(
+ Counters,
+ [{read_pkg, RPkg},
+ {read_byte, RByte},
+ {write_pkg, SPkg},
+ {write_byte, SByte},
+ {read_tries, any},
+ {write_tries, any},
+ {read_pkg_max, any},
+ {write_pkg_max, any}])
+ catch
+ C:E:S ->
+ ?SEV_EPRINT("Failed get socket info: "
+ "~n Class: ~p"
+ "~n Error: ~p"
+ "~n Stack: ~p", [C, E, S]),
+ {error, {socket_info_failed, {C, E, S}}}
+ end
+ end},
+ #{desc => "announce ready (send_and_validate 2)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_and_validate),
+ ok
+ end},
+
+ #{desc => "await continue (recv_and_validate 2)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv_and_validate)
+ end},
+ #{desc => "recv (2)",
+ cmd => fun(#{sock := Sock,
+ recv := Recv,
+ read_pkg := RPkg,
+ read_byte := RByte} = State) ->
+ case Recv(Sock) of
+ {ok, Data} ->
+ ?SEV_IPRINT("recv ~p bytes", [size(Data)]),
+ {ok, State#{read_pkg => RPkg + 1,
+ read_byte => RByte + size(Data)}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "validate (recv 2)",
+ cmd => fun(#{sock := Sock,
+ read_pkg := RPkg,
+ read_byte := RByte,
+ write_pkg := SPkg,
+ write_byte := SByte} = _State) ->
+ try socket:info(Sock) of
+ #{counters := Counters} ->
+ ?SEV_IPRINT("validate counters: "
+ "~s", [format_counters(Counters)]),
+ traffic_sar_counters_validation(
+ Counters,
+ [{read_pkg, RPkg},
+ {read_byte, RByte},
+ {write_pkg, SPkg},
+ {write_byte, SByte},
+ {read_tries, any},
+ {write_tries, any},
+ {read_pkg_max, any},
+ {write_pkg_max, any}])
+ catch
+ C:E:S ->
+ ?SEV_EPRINT("Failed get socket info: "
+ "~n Class: ~p"
+ "~n Error: ~p"
+ "~n Stack: ~p", [C, E, S]),
+ {error, {socket_info_failed, {C, E, S}}}
+ end
+ end},
+ #{desc => "announce ready (recv_and_validate 2)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_and_validate),
+ ok
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close connection socket",
+ cmd => fun(#{domain := local,
+ sock := Sock,
+ local_sa := #{path := Path}} = State) ->
+ ok = socket:close(Sock),
+ State1 =
+ unlink_path(Path,
+ fun() ->
+ maps:remove(local_sa, State)
+ end,
+ fun() -> State end),
+ {ok, maps:remove(sock, State1)};
+ (#{sock := Sock} = State) ->
+ socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{domain := local,
+ server := Pid} = State) ->
+ {ok, Path} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{path => Path}};
+ (#{server := Pid} = State) ->
+ {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{port => Port}}
+ end},
+
+ %% Start the client
+ #{desc => "order client start",
+ cmd => fun(#{domain := local,
+ client := Pid,
+ path := Path} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Path),
+ ok;
+ (#{client := Pid,
+ port := Port} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Port),
+ ok
+ end},
+ #{desc => "await client ready (init)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client, init)
+ end},
+
+ %% *** The actual test ***
+
+ #{desc => "order server to continue (with accept)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, accept),
+ ok
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "order client to continue (with connect)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, connect),
+ ok
+ end},
+ #{desc => "await client ready (connect)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, connect)
+ end},
+ #{desc => "await server ready (accept)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, accept)
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "order server to continue (recv_and_validate 1)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, recv_and_validate),
+ ok
+ end},
+ #{desc => "order client to continue (send_and_validate 1)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send_and_validate),
+ ok
+ end},
+ #{desc => "await client ready (send_and_validate 1)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, send_and_validate)
+ end},
+ #{desc => "await server ready (recv_and_validate 1)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv_and_validate)
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "order client to continue (recv_and_validate 1)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, recv_and_validate),
+ ok
+ end},
+ #{desc => "order server to continue (send_and_validate 1)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, send_and_validate),
+ ok
+ end},
+ #{desc => "await server ready (send_and_validate 1)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, send_and_validate)
+ end},
+ #{desc => "await client ready (recv_and_validate 1)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, recv_and_validate)
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "order server to continue (recv_and_validate 2)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, recv_and_validate),
+ ok
+ end},
+ #{desc => "order client to continue (send_and_validate 2)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send_and_validate),
+ ok
+ end},
+ #{desc => "await client ready (send_and_validate 2)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, send_and_validate)
+ end},
+ #{desc => "await server ready (recv_and_validate 2)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv_and_validate)
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "order client to continue (recv_and_validate 2)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, recv_and_validate),
+ ok
+ end},
+ #{desc => "order server to continue (send_and_validate 2)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, send_and_validate),
+ ok
+ end},
+ #{desc => "await server ready (send_and_validate 2)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, send_and_validate)
+ end},
+ #{desc => "await client ready (recv_and_validate 2)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, recv_and_validate)
+ end},
+
+ %% *** Termination ***
+ #{desc => "order client to terminate",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await client termination",
+ cmd => fun(#{client := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(client, State),
+ {ok, State1}
+ end},
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Server),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Server} = State) ->
+ ?SEV_AWAIT_TERMINATION(Server),
+ State1 = maps:remove(server, State),
+ {ok, State1}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start server evaluator"),
+ ServerInitState = InitState#{host => local_host()},
+ Server = ?SEV_START("server", ServerSeq, ServerInitState),
+
+ i("start client evaluator(s)"),
+ ClientInitState = InitState#{host => local_host()},
+ Client = ?SEV_START("client", ClientSeq, ClientInitState),
+
+ i("start 'tester' evaluator"),
+ TesterInitState = #{server => Server#ev.pid,
+ client => Client#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]).
+
+
+format_counters(Counters) ->
+ format_counters(traffic, Counters).
+
+format_counters(Type, Counters) when (Type =:= listen) orelse (Type =:= traffic) ->
+ format_counters(" ", Type, Counters).
+
+format_counters(Prefix, traffic, Counters) ->
+ ReadByte = maps:get(read_byte, Counters, -1),
+ ReadFails = maps:get(read_fails, Counters, -1),
+ ReadPkg = maps:get(read_pkg, Counters, -1),
+ ReadPkgMax = maps:get(read_pkg_max, Counters, -1),
+ ReadTries = maps:get(read_tries, Counters, -1),
+ ReadWaits = maps:get(read_waits, Counters, -1),
+ WriteByte = maps:get(write_byte, Counters, -1),
+ WriteFails = maps:get(write_fails, Counters, -1),
+ WritePkg = maps:get(write_pkg, Counters, -1),
+ WritePkgMax = maps:get(write_pkg_max, Counters, -1),
+ WriteTries = maps:get(write_tries, Counters, -1),
+ WriteWaits = maps:get(write_waits, Counters, -1),
+ f("~n~sNumber Of Read Bytes: ~p"
+ "~n~sNumber Of Read Fails: ~p"
+ "~n~sNumber Of Read Packages: ~p"
+ "~n~sNumber Of Read Tries: ~p"
+ "~n~sNumber Of Read Waits: ~p"
+ "~n~sMax Read Package Size: ~p"
+ "~n~sNumber Of Write Bytes: ~p"
+ "~n~sNumber Of Write Fails: ~p"
+ "~n~sNumber Of Write Packages: ~p"
+ "~n~sNumber Of Write Tries: ~p"
+ "~n~sNumber Of Write Waits: ~p"
+ "~n~sMax Write Package Size: ~p",
+ [Prefix, ReadByte,
+ Prefix, ReadFails,
+ Prefix, ReadPkg,
+ Prefix, ReadTries,
+ Prefix, ReadWaits,
+ Prefix, ReadPkgMax,
+ Prefix, WriteByte,
+ Prefix, WriteFails,
+ Prefix, WritePkg,
+ Prefix, WriteTries,
+ Prefix, WriteWaits,
+ Prefix, WritePkgMax]);
+
+format_counters(Prefix, listen, Counters) ->
+ AccSuccess = maps:get(acc_success, Counters, -1),
+ AccFails = maps:get(acc_fails, Counters, -1),
+ AccTries = maps:get(acc_tries, Counters, -1),
+ AccWaits = maps:get(acc_waits, Counters, -1),
+ f("~n~sNumber Of Successful Accepts: ~p"
+ "~n~sNumber Of Failed Accepts: ~p"
+ "~n~sNumber Of Accept Attempts: ~p"
+ "~n~sNumber Of Accept Waits: ~p",
+ [Prefix, AccSuccess,
+ Prefix, AccFails,
+ Prefix, AccTries,
+ Prefix, AccWaits]).
+
+all_counters() ->
+ [
+ read_byte,
+ read_fails,
+ read_pkg,
+ read_pkg_max,
+ read_tries,
+ read_waits,
+ write_byte,
+ write_fails,
+ write_pkg,
+ write_pkg_max,
+ write_tries,
+ write_waits,
+ acc_success,
+ acc_fails,
+ acc_tries,
+ acc_waits
+ ].
+
+zero_counters() ->
+ [{Cnt, 0} || Cnt <- all_counters()].
+
+any_counters() ->
+ [{Cnt, any} || Cnt <- all_counters()].
+
+
+%% This function ensures that we have a list of "validate counters"
+%% that have an entry for each existing counter.
+
+ensure_counters(Counters) ->
+ ensure_counters(any_counters(), Counters, []).
+
+ensure_counters([], [], Acc) ->
+ lists:reverse(Acc);
+ensure_counters([{Cnt, Val}|DefCounters], Counters, Acc) ->
+ case lists:keysearch(Cnt, 1, Counters) of
+ {value, {Cnt, _} = T} ->
+ Counters2 = lists:keydelete(Cnt, 1, Counters),
+ ensure_counters(DefCounters, Counters2, [T|Acc]);
+ false ->
+ ensure_counters(DefCounters, Counters, [{Cnt, Val}|Acc])
+ end.
+
+traffic_sar_counters_validation(Counters) ->
+ %% ?SEV_IPRINT("traffic_sar_counters_validation -> entry with"
+ %% "~n Counters: ~p", [Counters]),
+ traffic_sar_counters_validation2(maps:to_list(Counters),
+ zero_counters()).
+
+traffic_sar_counters_validation(Counters, ValidateCounters) ->
+ %% ?SEV_IPRINT("traffic_sar_counters_validation -> entry with"
+ %% "~n Counters: ~p"
+ %% "~n Validate Counters: ~p", [Counters, ValidateCounters]),
+ traffic_sar_counters_validation2(maps:to_list(Counters),
+ ensure_counters(ValidateCounters)).
+
+traffic_sar_counters_validation2(Counters, []) ->
+ %% ?SEV_IPRINT("traffic_sar_counters_validation2 -> Remaining Counters: "
+ %% "~n ~p", [Counters]),
+ (catch lists:foreach(
+ fun({_Cnt, 0}) -> ok;
+ ({Cnt, Val}) ->
+ throw({error, {invalid_counter, Cnt, Val}})
+ end,
+ Counters));
+traffic_sar_counters_validation2(Counters, [{Cnt, Val}|ValidateCounters]) ->
+ %% ?SEV_IPRINT("traffic_sar_counters_validation2 -> try validate ~w when"
+ %% "~n Counters: ~p"
+ %% "~n ValidateCounters: ~p", [Cnt, Counters, ValidateCounters]),
+ case lists:keysearch(Cnt, 1, Counters) of
+ {value, {Cnt, Val}} ->
+ %% ?SEV_IPRINT("traffic_sar_counters_validation2 -> ~w validated", [Cnt]),
+ Counters2 = lists:keydelete(Cnt, 1, Counters),
+ traffic_sar_counters_validation2(Counters2, ValidateCounters);
+ {value, {Cnt, _Val}} when (Val =:= any) ->
+ %% ?SEV_IPRINT("traffic_sar_counters_validation2 -> "
+ %% "~w validated (any) when"
+ %% "~n Counters: ~p", [Cnt, Counters]),
+ Counters2 = lists:keydelete(Cnt, 1, Counters),
+ traffic_sar_counters_validation2(Counters2, ValidateCounters);
+ {value, {Cnt, InvVal}} ->
+ ?SEV_EPRINT("traffic_sar_counters_validation2 -> "
+ "~w validation failed: "
+ "~n Expected Value: ~p"
+ "~n Actual Value: ~p", [Cnt, Val, InvVal]),
+ {error, {invalid_counter, Cnt, InvVal, Val}};
+ false ->
+ ?SEV_EPRINT("traffic_sar_counters_validation2 -> "
+ "~w validation failed: Unknown", [Cnt]),
+ {error, {unknown_counter, Cnt, Counters}}
+ end.
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to (simply) test the counters
+%% for both read and write.
+%% So that its easy to extend, we use fun's for read and write.
+%% We use UDP on IPv4.
+
+traffic_sendto_and_recvfrom_counters_udp4(suite) ->
+ [];
+traffic_sendto_and_recvfrom_counters_udp4(doc) ->
+ [];
+traffic_sendto_and_recvfrom_counters_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(15)),
+ tc_try(traffic_sendto_and_recvfrom_counters_udp4,
+ fun() ->
+ InitState = #{domain => inet,
+ proto => udp,
+ recv => fun(S) ->
+ socket:recvfrom(S)
+ end,
+ send => fun(S, Data, Dest) ->
+ socket:sendto(S, Data, Dest)
+ end},
+ ok = traffic_send_and_recv_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to (simply) test the counters
+%% for both read and write.
+%% So that its easy to extend, we use fun's for read and write.
+%% We use UDP on IPv6.
+
+traffic_sendto_and_recvfrom_counters_udp6(suite) ->
+ [];
+traffic_sendto_and_recvfrom_counters_udp6(doc) ->
+ [];
+traffic_sendto_and_recvfrom_counters_udp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(15)),
+ tc_try(traffic_sendto_and_recvfrom_counters_udp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet6,
+ proto => udp,
+ recv => fun(S) ->
+ socket:recvfrom(S)
+ end,
+ send => fun(S, Data, Dest) ->
+ socket:sendto(S, Data, Dest)
+ end},
+ ok = traffic_send_and_recv_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to (simply) test the counters
+%% for both read and write.
+%% So that its easy to extend, we use fun's for read and write.
+%% We use default (UDP) on local.
+
+traffic_sendto_and_recvfrom_counters_udpL(suite) ->
+ [];
+traffic_sendto_and_recvfrom_counters_udpL(doc) ->
+ [];
+traffic_sendto_and_recvfrom_counters_udpL(_Config) when is_list(_Config) ->
+ ?TT(?SECS(15)),
+ tc_try(traffic_sendto_and_recvfrom_counters_udp4,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ InitState = #{domain => local,
+ proto => default,
+ recv => fun(S) ->
+ socket:recvfrom(S)
+ end,
+ send => fun(S, Data, Dest) ->
+ socket:sendto(S, Data, Dest)
+ end},
+ ok = traffic_send_and_recv_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to (simply) test the counters
+%% for both read and write.
+%% So that its easy to extend, we use fun's for read and write.
+%% We use UDP on IPv4.
+
+traffic_sendmsg_and_recvmsg_counters_udp4(suite) ->
+ [];
+traffic_sendmsg_and_recvmsg_counters_udp4(doc) ->
+ [];
+traffic_sendmsg_and_recvmsg_counters_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(15)),
+ tc_try(traffic_sendmsg_and_recvmsg_counters_udp4,
+ fun() ->
+ InitState = #{domain => inet,
+ proto => udp,
+ recv => fun(S) ->
+ case socket:recvmsg(S) of
+ {ok, #{addr := Source,
+ iov := [Data]}} ->
+ {ok, {Source, Data}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ send => fun(S, Data, Dest) ->
+ MsgHdr = #{addr => Dest,
+ iov => [Data]},
+ socket:sendmsg(S, MsgHdr)
+ end},
+ ok = traffic_send_and_recv_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to (simply) test the counters
+%% for both read and write.
+%% So that its easy to extend, we use fun's for read and write.
+%% We use UDP on IPv6.
+
+traffic_sendmsg_and_recvmsg_counters_udp6(suite) ->
+ [];
+traffic_sendmsg_and_recvmsg_counters_udp6(doc) ->
+ [];
+traffic_sendmsg_and_recvmsg_counters_udp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(15)),
+ tc_try(traffic_sendmsg_and_recvmsg_counters_udp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet6,
+ proto => udp,
+ recv => fun(S) ->
+ case socket:recvmsg(S) of
+ {ok, #{addr := Source,
+ iov := [Data]}} ->
+ {ok, {Source, Data}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ send => fun(S, Data, Dest) ->
+ MsgHdr = #{addr => Dest,
+ iov => [Data]},
+ socket:sendmsg(S, MsgHdr)
+ end},
+ ok = traffic_send_and_recv_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to (simply) test the counters
+%% for both read and write.
+%% So that its easy to extend, we use fun's for read and write.
+%% We use default (UDP) on local.
+
+traffic_sendmsg_and_recvmsg_counters_udpL(suite) ->
+ [];
+traffic_sendmsg_and_recvmsg_counters_udpL(doc) ->
+ [];
+traffic_sendmsg_and_recvmsg_counters_udpL(_Config) when is_list(_Config) ->
+ ?TT(?SECS(15)),
+ tc_try(traffic_sendmsg_and_recvmsg_counters_udpL,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ InitState = #{domain => local,
+ proto => default,
+ recv => fun(S) ->
+ case socket:recvmsg(S) of
+ {ok, #{addr := Source,
+ iov := [Data]}} ->
+ {ok, {Source, Data}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ send => fun(S, Data, Dest) ->
+ MsgHdr = #{addr => Dest,
+ iov => [Data]},
+ socket:sendmsg(S, MsgHdr)
+ end},
+ ok = traffic_send_and_recv_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+traffic_send_and_recv_udp(InitState) ->
+ ServerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain, proto := Proto} = State) ->
+ case socket:open(Domain, dgram, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{domain := local,
+ sock := Sock,
+ local_sa := LSA} = _State) ->
+ case socket:bind(Sock, LSA) of
+ {ok, _Port} ->
+ ok; % We do not care about the port for local
+ {error, _} = ERROR ->
+ ERROR
+ end;
+ (#{sock := LSock,
+ local_sa := LSA} = State) ->
+ case sock_bind(LSock, LSA) of
+ {ok, Port} ->
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "initial counter validation (=zero)",
+ cmd => fun(#{sock := Sock} = _State) ->
+ try socket:info(Sock) of
+ #{counters := Counters} ->
+ ?SEV_IPRINT("Validate initial counters: "
+ "~s", [format_counters(Counters)]),
+ traffic_sar_counters_validation(Counters)
+ catch
+ C:E:S ->
+ ?SEV_EPRINT("Failed get socket info: "
+ "~n Class: ~p"
+ "~n Error: ~p"
+ "~n Stack: ~p", [C, E, S]),
+ {error, {socket_info_failed, {C, E, S}}}
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{domain := local,
+ tester := Tester,
+ local_sa := #{path := Path}}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Path),
+ ok;
+ (#{tester := Tester,
+ lport := Port}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Port),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (recv_and_validate 1)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv_and_validate)
+ end},
+ #{desc => "recv (1)",
+ cmd => fun(#{sock := Sock,
+ recv := Recv} = State) ->
+ case Recv(Sock) of
+ {ok, {ClientSA, Data}} ->
+ ?SEV_IPRINT("recv ~p bytes", [size(Data)]),
+ {ok, State#{client_sa => ClientSA,
+ read_pkg => 1,
+ read_byte => size(Data)}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "validate (recv 1)",
+ cmd => fun(#{sock := Sock,
+ read_pkg := Pkg,
+ read_byte := Byte} = _State) ->
+ try socket:info(Sock) of
+ #{counters := Counters} ->
+ ?SEV_IPRINT("validate counters: "
+ "~s", [format_counters(Counters)]),
+ traffic_sar_counters_validation(
+ Counters,
+ [{read_pkg, Pkg},
+ {read_byte, Byte},
+ {read_tries, any}])
+ catch
+ C:E:S ->
+ ?SEV_EPRINT("Failed get socket info: "
+ "~n Class: ~p"
+ "~n Error: ~p"
+ "~n Stack: ~p", [C, E, S]),
+ {error, {socket_info_failed, {C, E, S}}}
+ end
+ end},
+ #{desc => "announce ready (recv_and_validate 1)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_and_validate),
+ ok
+ end},
+
+ #{desc => "await continue (send_and_validate 1)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_and_validate)
+ end},
+ #{desc => "send (1)",
+ cmd => fun(#{sock := Sock,
+ send := Send,
+ client_sa := ClientSA} = State) ->
+ Data = ?DATA,
+ case Send(Sock, Data, ClientSA) of
+ ok ->
+ ?SEV_IPRINT("sent ~p bytes", [size(Data)]),
+ {ok, State#{write_pkg => 1,
+ write_byte => size(Data)}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "validate (send 1)",
+ cmd => fun(#{sock := Sock,
+ read_pkg := RPkg,
+ read_byte := RByte,
+ write_pkg := SPkg,
+ write_byte := SByte} = _State) ->
+ try socket:info(Sock) of
+ #{counters := Counters} ->
+ ?SEV_IPRINT("validate counters: "
+ "~s", [format_counters(Counters)]),
+ traffic_sar_counters_validation(
+ Counters,
+ [{read_pkg, RPkg},
+ {read_byte, RByte},
+ {write_pkg, SPkg},
+ {write_byte, SByte},
+ {read_tries, any},
+ {write_tries, any}])
+ catch
+ C:E:S ->
+ ?SEV_EPRINT("Failed get socket info: "
+ "~n Class: ~p"
+ "~n Error: ~p"
+ "~n Stack: ~p", [C, E, S]),
+ {error, {socket_info_failed, {C, E, S}}}
+ end
+ end},
+ #{desc => "announce ready (send_and_validate 1)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_and_validate),
+ ok
+ end},
+
+ #{desc => "await continue (recv_and_validate 2)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv_and_validate)
+ end},
+ #{desc => "recv (2)",
+ cmd => fun(#{sock := Sock,
+ recv := Recv,
+ read_pkg := Pkg,
+ read_byte := Byte} = State) ->
+ case Recv(Sock) of
+ {ok, {Source, Data}} ->
+ ?SEV_IPRINT("recv ~p bytes", [size(Data)]),
+ {ok, State#{client_sa => Source,
+ read_pkg => Pkg + 1,
+ read_byte => Byte + size(Data)}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "validate (recv 2)",
+ cmd => fun(#{sock := Sock,
+ read_pkg := RPkg,
+ read_byte := RByte,
+ write_pkg := SPkg,
+ write_byte := SByte} = _State) ->
+ try socket:info(Sock) of
+ #{counters := Counters} ->
+ ?SEV_IPRINT("validate counters: "
+ "~s", [format_counters(Counters)]),
+ traffic_sar_counters_validation(
+ Counters,
+ [{read_pkg, RPkg},
+ {read_byte, RByte},
+ {write_pkg, SPkg},
+ {write_byte, SByte},
+ {read_tries, any},
+ {write_tries, any}])
+ catch
+ C:E:S ->
+ ?SEV_EPRINT("Failed get socket info: "
+ "~n Class: ~p"
+ "~n Error: ~p"
+ "~n Stack: ~p", [C, E, S]),
+ {error, {socket_info_failed, {C, E, S}}}
+ end
+ end},
+ #{desc => "announce ready (recv_and_validate 2)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_and_validate),
+ ok
+ end},
+
+ #{desc => "await continue (send_and_validate 2)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_and_validate)
+ end},
+ #{desc => "send (2)",
+ cmd => fun(#{sock := Sock,
+ client_sa := ClientSA,
+ send := Send,
+ write_pkg := Pkg,
+ write_byte := Byte} = State) ->
+ Data = ?DATA,
+ case Send(Sock, Data, ClientSA) of
+ ok ->
+ ?SEV_IPRINT("sent ~p bytes", [size(Data)]),
+ {ok, State#{write_pkg => Pkg + 1,
+ write_byte => Byte + size(Data)}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "validate (send 2)",
+ cmd => fun(#{sock := Sock,
+ read_pkg := RPkg,
+ read_byte := RByte,
+ write_pkg := SPkg,
+ write_byte := SByte} = _State) ->
+ try socket:info(Sock) of
+ #{counters := Counters} ->
+ ?SEV_IPRINT("validate counters: "
+ "~s", [format_counters(Counters)]),
+ traffic_sar_counters_validation(
+ Counters,
+ [{read_pkg, RPkg},
+ {read_byte, RByte},
+ {write_pkg, SPkg},
+ {write_byte, SByte},
+ {read_tries, any},
+ {write_tries, any}])
+ catch
+ C:E:S ->
+ ?SEV_EPRINT("Failed get socket info: "
+ "~n Class: ~p"
+ "~n Error: ~p"
+ "~n Stack: ~p", [C, E, S]),
+ {error, {socket_info_failed, {C, E, S}}}
+ end
+ end},
+ #{desc => "announce ready (send_and_validate 2)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_and_validate),
+ ok
+ end},
+
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket (just in case)",
+ cmd => fun(#{domain := local,
+ sock := Sock,
+ local_sa := #{path := Path}} = State) ->
+ ok = socket:close(Sock),
+ State1 =
+ unlink_path(Path,
+ fun() ->
+ maps:remove(local_sa, State)
+ end,
+ fun() -> State end),
+ {ok, maps:remove(lsock, State1)};
+ (#{sock := Sock} = State) ->
+ (catch socket:close(Sock)),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ ClientSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start (from tester)",
+ cmd => fun(#{domain := local} = State) ->
+ {Tester, Path} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, server_path => Path}};
+ (State) ->
+ {Tester, Port} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, server_port => Port}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which server (local) address",
+ cmd => fun(#{domain := local = Domain,
+ server_path := Path} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ SSA = #{family => Domain, path => Path},
+ {ok, State#{local_sa => LSA, server_sa => SSA}};
+ (#{domain := Domain, server_port := Port} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ SSA = LSA#{port => Port},
+ {ok, State#{local_sa => LSA, server_sa => SSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain,
+ proto := Proto} = State) ->
+ case socket:open(Domain, dgram, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{sock := Sock, local_sa := LSA} = _State) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "initial counter validation (=zero)",
+ cmd => fun(#{sock := Sock} = _State) ->
+ try socket:info(Sock) of
+ #{counters := Counters} ->
+ ?SEV_IPRINT("Validate initial counters: "
+ "~s", [format_counters(Counters)]),
+ traffic_sar_counters_validation(Counters)
+ catch
+ C:E:S ->
+ ?SEV_EPRINT("Failed get socket info: "
+ "~n Class: ~p"
+ "~n Error: ~p"
+ "~n Stack: ~p", [C, E, S]),
+ {error, {socket_info_failed, {C, E, S}}}
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (send_and_validate 1)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_and_validate)
+ end},
+ #{desc => "send (1)",
+ cmd => fun(#{sock := Sock,
+ send := Send,
+ server_sa := ServerSA} = State) ->
+ Data = ?DATA,
+ case Send(Sock, Data, ServerSA) of
+ ok ->
+ ?SEV_IPRINT("sent ~p bytes", [size(Data)]),
+ {ok, State#{write_pkg => 1,
+ write_byte => size(Data)}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "validate (send 1)",
+ cmd => fun(#{sock := Sock,
+ write_pkg := SPkg,
+ write_byte := SByte} = _State) ->
+ try socket:info(Sock) of
+ #{counters := Counters} ->
+ ?SEV_IPRINT("validate counters: "
+ "~s", [format_counters(Counters)]),
+ traffic_sar_counters_validation(
+ Counters,
+ [{write_pkg, SPkg},
+ {write_byte, SByte},
+ {write_tries, any},
+ {write_pkg_max, any}])
+ catch
+ C:E:S ->
+ ?SEV_EPRINT("Failed get socket info: "
+ "~n Class: ~p"
+ "~n Error: ~p"
+ "~n Stack: ~p", [C, E, S]),
+ {error, {socket_info_failed, {C, E, S}}}
+ end
+ end},
+ #{desc => "announce ready (send_and_validate 1)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_and_validate),
+ ok
+ end},
+
+ #{desc => "await continue (recv_and_validate 1)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv_and_validate)
+ end},
+ #{desc => "recv (1)",
+ cmd => fun(#{sock := Sock,
+ recv := Recv,
+ server_sa := #{family := local} = ServerSA} = State) ->
+ case Recv(Sock) of
+ {ok, {ServerSA, Data}} ->
+ ?SEV_IPRINT("recv ~p bytes", [size(Data)]),
+ {ok, State#{read_pkg => 1,
+ read_byte => size(Data)}};
+ {error, _} = ERROR ->
+ ERROR
+ end;
+ (#{sock := Sock,
+ recv := Recv,
+ server_sa := #{addr := Addr, port := Port}} = State) ->
+ case Recv(Sock) of
+ {ok, {#{addr := Addr, port := Port}, Data}} ->
+ ?SEV_IPRINT("recv ~p bytes", [size(Data)]),
+ {ok, State#{read_pkg => 1,
+ read_byte => size(Data)}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "validate (recv 1)",
+ cmd => fun(#{sock := Sock,
+ read_pkg := RPkg,
+ read_byte := RByte,
+ write_pkg := SPkg,
+ write_byte := SByte} = _State) ->
+ try socket:info(Sock) of
+ #{counters := Counters} ->
+ ?SEV_IPRINT("validate counters: "
+ "~s", [format_counters(Counters)]),
+ traffic_sar_counters_validation(
+ Counters,
+ [{read_pkg, RPkg},
+ {read_byte, RByte},
+ {write_pkg, SPkg},
+ {write_byte, SByte},
+ {read_tries, any},
+ {write_tries, any},
+ {read_pkg_max, any},
+ {write_pkg_max, any}])
+ catch
+ C:E:S ->
+ ?SEV_EPRINT("Failed get socket info: "
+ "~n Class: ~p"
+ "~n Error: ~p"
+ "~n Stack: ~p", [C, E, S]),
+ {error, {socket_info_failed, {C, E, S}}}
+ end
+ end},
+ #{desc => "announce ready (recv_and_validate 1)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_and_validate),
+ ok
+ end},
+
+ #{desc => "await continue (send_and_validate 2)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_and_validate)
+ end},
+ #{desc => "send (2)",
+ cmd => fun(#{sock := Sock,
+ send := Send,
+ server_sa := ServerSA,
+ write_pkg := SPkg,
+ write_byte := SByte} = State) ->
+ Data = ?DATA,
+ case Send(Sock, Data, ServerSA) of
+ ok ->
+ ?SEV_IPRINT("sent ~p bytes", [size(Data)]),
+ {ok, State#{write_pkg => SPkg + 1,
+ write_byte => SByte + size(Data)}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "validate (send 2)",
+ cmd => fun(#{sock := Sock,
+ read_pkg := RPkg,
+ read_byte := RByte,
+ write_pkg := SPkg,
+ write_byte := SByte} = _State) ->
+ try socket:info(Sock) of
+ #{counters := Counters} ->
+ ?SEV_IPRINT("validate counters: "
+ "~s", [format_counters(Counters)]),
+ traffic_sar_counters_validation(
+ Counters,
+ [{read_pkg, RPkg},
+ {read_byte, RByte},
+ {write_pkg, SPkg},
+ {write_byte, SByte},
+ {read_tries, any},
+ {write_tries, any},
+ {read_pkg_max, any},
+ {write_pkg_max, any}])
+ catch
+ C:E:S ->
+ ?SEV_EPRINT("Failed get socket info: "
+ "~n Class: ~p"
+ "~n Error: ~p"
+ "~n Stack: ~p", [C, E, S]),
+ {error, {socket_info_failed, {C, E, S}}}
+ end
+ end},
+ #{desc => "announce ready (send_and_validate 2)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_and_validate),
+ ok
+ end},
+
+ #{desc => "await continue (recv_and_validate 2)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv_and_validate)
+ end},
+ #{desc => "recv (2)",
+ cmd => fun(#{sock := Sock,
+ server_sa := #{family := local} = ServerSA,
+ recv := Recv,
+ read_pkg := RPkg,
+ read_byte := RByte} = State) ->
+ case Recv(Sock) of
+ {ok, {ServerSA, Data}} ->
+ ?SEV_IPRINT("recv ~p bytes", [size(Data)]),
+ {ok, State#{read_pkg => RPkg + 1,
+ read_byte => RByte + size(Data)}};
+ {error, _} = ERROR ->
+ ERROR
+ end;
+ (#{sock := Sock,
+ server_sa := #{addr := Addr, port := Port},
+ recv := Recv,
+ read_pkg := RPkg,
+ read_byte := RByte} = State) ->
+ case Recv(Sock) of
+ {ok, {#{addr := Addr, port := Port}, Data}} ->
+ ?SEV_IPRINT("recv ~p bytes", [size(Data)]),
+ {ok, State#{read_pkg => RPkg + 1,
+ read_byte => RByte + size(Data)}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "validate (recv 2)",
+ cmd => fun(#{sock := Sock,
+ read_pkg := RPkg,
+ read_byte := RByte,
+ write_pkg := SPkg,
+ write_byte := SByte} = _State) ->
+ try socket:info(Sock) of
+ #{counters := Counters} ->
+ ?SEV_IPRINT("validate counters: "
+ "~s", [format_counters(Counters)]),
+ traffic_sar_counters_validation(
+ Counters,
+ [{read_pkg, RPkg},
+ {read_byte, RByte},
+ {write_pkg, SPkg},
+ {write_byte, SByte},
+ {read_tries, any},
+ {write_tries, any},
+ {read_pkg_max, any},
+ {write_pkg_max, any}])
+ catch
+ C:E:S ->
+ ?SEV_EPRINT("Failed get socket info: "
+ "~n Class: ~p"
+ "~n Error: ~p"
+ "~n Stack: ~p", [C, E, S]),
+ {error, {socket_info_failed, {C, E, S}}}
+ end
+ end},
+ #{desc => "announce ready (recv_and_validate 2)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_and_validate),
+ ok
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close connection socket",
+ cmd => fun(#{domain := local,
+ sock := Sock,
+ local_sa := #{path := Path}} = State) ->
+ ok = socket:close(Sock),
+ State1 =
+ unlink_path(Path,
+ fun() ->
+ maps:remove(local_sa, State)
+ end,
+ fun() -> State end),
+ {ok, maps:remove(sock, State1)};
+ (#{sock := Sock} = State) ->
+ socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{domain := local,
+ server := Pid} = State) ->
+ {ok, Path} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{path => Path}};
+ (#{server := Pid} = State) ->
+ {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{port => Port}}
+ end},
+
+ %% Start the client
+ #{desc => "order client start",
+ cmd => fun(#{domain := local,
+ client := Pid,
+ path := Path} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Path),
+ ok;
+ (#{client := Pid,
+ port := Port} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Port),
+ ok
+ end},
+ #{desc => "await client ready (init)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client, init)
+ end},
+
+ %% *** The actual test ***
+
+ #{desc => "order server to continue (recv_and_validate 1)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, recv_and_validate),
+ ok
+ end},
+ #{desc => "order client to continue (send_and_validate 1)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send_and_validate),
+ ok
+ end},
+ #{desc => "await client ready (send_and_validate 1)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, send_and_validate)
+ end},
+ #{desc => "await server ready (recv_and_validate 1)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv_and_validate)
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "order client to continue (recv_and_validate 1)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, recv_and_validate),
+ ok
+ end},
+ #{desc => "order server to continue (send_and_validate 1)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, send_and_validate),
+ ok
+ end},
+ #{desc => "await server ready (send_and_validate 1)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, send_and_validate)
+ end},
+ #{desc => "await client ready (recv_and_validate 1)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, recv_and_validate)
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "order server to continue (recv_and_validate 2)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, recv_and_validate),
+ ok
+ end},
+ #{desc => "order client to continue (send_and_validate 2)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send_and_validate),
+ ok
+ end},
+ #{desc => "await client ready (send_and_validate 2)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, send_and_validate)
+ end},
+ #{desc => "await server ready (recv_and_validate 2)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv_and_validate)
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "order client to continue (recv_and_validate 2)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, recv_and_validate),
+ ok
+ end},
+ #{desc => "order server to continue (send_and_validate 2)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, send_and_validate),
+ ok
+ end},
+ #{desc => "await server ready (send_and_validate 2)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, send_and_validate)
+ end},
+ #{desc => "await client ready (recv_and_validate 2)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, recv_and_validate)
+ end},
+
+ %% *** Termination ***
+ #{desc => "order client to terminate",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await client termination",
+ cmd => fun(#{client := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(client, State),
+ {ok, State1}
+ end},
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Server),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Server} = State) ->
+ ?SEV_AWAIT_TERMINATION(Server),
+ State1 = maps:remove(server, State),
+ {ok, State1}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start server evaluator"),
+ ServerInitState = InitState#{host => local_host()},
+ Server = ?SEV_START("server", ServerSeq, ServerInitState),
+
+ i("start client evaluator(s)"),
+ ClientInitState = InitState#{host => local_host()},
+ Client = ?SEV_START("client", ClientSeq, ClientInitState),
+
+ i("start 'tester' evaluator"),
+ TesterInitState = #{server => Server#ev.pid,
+ client => Client#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]).
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the send and recv functions
+%% behave as expected when sending and/or reading chunks.
+%% First send data in one "big" chunk, and read it in "small" chunks.
+%% Second, send in a bunch of "small" chunks, and read in one "big" chunk.
+%% Socket is IPv4.
+
+traffic_send_and_recv_chunks_tcp4(suite) ->
+ [];
+traffic_send_and_recv_chunks_tcp4(doc) ->
+ [];
+traffic_send_and_recv_chunks_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(30)),
+ tc_try(traffic_send_and_recv_chunks_tcp4,
+ fun() ->
+ InitState = #{domain => inet,
+ proto => tcp},
+ ok = traffic_send_and_recv_chunks_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the send and recv functions
+%% behave as expected when sending and/or reading chunks.
+%% First send data in one "big" chunk, and read it in "small" chunks.
+%% Second, send in a bunch of "small" chunks, and read in one "big" chunk.
+%% Socket is IPv6.
+
+traffic_send_and_recv_chunks_tcp6(suite) ->
+ [];
+traffic_send_and_recv_chunks_tcp6(doc) ->
+ [];
+traffic_send_and_recv_chunks_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(30)),
+ tc_try(traffic_send_and_recv_chunks_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet6,
+ proto => tcp},
+ ok = traffic_send_and_recv_chunks_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the send and recv functions
+%% behave as expected when sending and/or reading chunks.
+%% First send data in one "big" chunk, and read it in "small" chunks.
+%% Second, send in a bunch of "small" chunks, and read in one "big" chunk.
+%% Socket is UNix Domain (Stream) socket.
+
+traffic_send_and_recv_chunks_tcpL(suite) ->
+ [];
+traffic_send_and_recv_chunks_tcpL(doc) ->
+ [];
+traffic_send_and_recv_chunks_tcpL(_Config) when is_list(_Config) ->
+ ?TT(?SECS(30)),
+ tc_try(traffic_send_and_recv_chunks_tcp6,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ InitState = #{domain => local,
+ proto => default},
+ ok = traffic_send_and_recv_chunks_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+traffic_send_and_recv_chunks_tcp(InitState) ->
+ ServerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "create listen socket",
+ cmd => fun(#{domain := Domain, proto := Proto} = State) ->
+ case socket:open(Domain, stream, Proto) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{domain := local,
+ lsock := LSock,
+ local_sa := LSA} = _State) ->
+ case socket:bind(LSock, LSA) of
+ {ok, _Port} ->
+ ok; % We do not care about the port for local
+ {error, _} = ERROR ->
+ ERROR
+ end;
+ (#{lsock := LSock,
+ local_sa := LSA} = State) ->
+ case sock_bind(LSock, LSA) of
+ {ok, Port} ->
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{domain := local,
+ tester := Tester,
+ local_sa := LSA}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, LSA),
+ ok;
+ (#{tester := Tester,
+ local_sa := LSA,
+ lport := Port}) ->
+ ServerSA = LSA#{port => Port},
+ ?SEV_ANNOUNCE_READY(Tester, init, ServerSA),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "accept",
+ cmd => fun(#{lsock := LSock} = State) ->
+ case socket:accept(LSock) of
+ {ok, Sock} ->
+ {ok, State#{csock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept),
+ ok
+ end},
+
+ #{desc => "await continue (recv-many-small)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv_many_small)
+ end},
+ #{desc => "recv chunk 1",
+ cmd => fun(#{csock := Sock} = State) ->
+ case socket:recv(Sock, 100) of
+ {ok, Chunk} ->
+ ?SEV_IPRINT("recv of chunk 1 of ~p bytes",
+ [size(Chunk)]),
+ {ok, State#{chunks => [b2l(Chunk)]}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "recv chunk 2",
+ cmd => fun(#{csock := Sock,
+ chunks := Chunks} = State) ->
+ case socket:recv(Sock, 100) of
+ {ok, Chunk} ->
+ ?SEV_IPRINT("recv of chunk 2 of ~p bytes",
+ [size(Chunk)]),
+ {ok, State#{chunks => [b2l(Chunk)|Chunks]}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "recv chunk 3",
+ cmd => fun(#{csock := Sock,
+ chunks := Chunks} = State) ->
+ case socket:recv(Sock, 100) of
+ {ok, Chunk} ->
+ ?SEV_IPRINT("recv of chunk 3 of ~p bytes",
+ [size(Chunk)]),
+ {ok, State#{chunks => [b2l(Chunk)|Chunks]}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "recv chunk 4",
+ cmd => fun(#{csock := Sock,
+ chunks := Chunks} = State) ->
+ case socket:recv(Sock, 100) of
+ {ok, Chunk} ->
+ ?SEV_IPRINT("recv of chunk 4 of ~p bytes",
+ [size(Chunk)]),
+ {ok, State#{chunks => [b2l(Chunk)|Chunks]}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "recv chunk 5",
+ cmd => fun(#{csock := Sock,
+ chunks := Chunks} = State) ->
+ case socket:recv(Sock, 100) of
+ {ok, Chunk} ->
+ ?SEV_IPRINT("recv of chunk 5 of ~p bytes",
+ [size(Chunk)]),
+ {ok, State#{chunks => [b2l(Chunk)|Chunks]}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "recv chunk 6",
+ cmd => fun(#{csock := Sock,
+ chunks := Chunks} = State) ->
+ case socket:recv(Sock, 100) of
+ {ok, Chunk} ->
+ ?SEV_IPRINT("recv of chunk 6 of ~p bytes",
+ [size(Chunk)]),
+ {ok, State#{chunks => [b2l(Chunk)|Chunks]}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "recv chunk 7",
+ cmd => fun(#{csock := Sock,
+ chunks := Chunks} = State) ->
+ case socket:recv(Sock, 100) of
+ {ok, Chunk} ->
+ ?SEV_IPRINT("recv of chunk 7 of ~p bytes",
+ [size(Chunk)]),
+ {ok, State#{chunks => [b2l(Chunk)|Chunks]}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "recv chunk 8",
+ cmd => fun(#{csock := Sock,
+ chunks := Chunks} = State) ->
+ case socket:recv(Sock, 100) of
+ {ok, Chunk} ->
+ ?SEV_IPRINT("recv of chunk 8 of ~p bytes",
+ [size(Chunk)]),
+ {ok, State#{chunks => [b2l(Chunk)|Chunks]}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "recv chunk 9",
+ cmd => fun(#{csock := Sock,
+ chunks := Chunks} = State) ->
+ case socket:recv(Sock, 100) of
+ {ok, Chunk} ->
+ ?SEV_IPRINT("recv of chunk 9 of ~p bytes",
+ [size(Chunk)]),
+ {ok, State#{chunks => [b2l(Chunk)|Chunks]}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "recv chunk 10",
+ cmd => fun(#{csock := Sock,
+ chunks := Chunks} = State) ->
+ case socket:recv(Sock, 100) of
+ {ok, Chunk} ->
+ ?SEV_IPRINT("recv of chunk 10 of ~p bytes",
+ [size(Chunk)]),
+ {ok, State#{chunks => [b2l(Chunk)|Chunks]}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv-many-small)",
+ cmd => fun(#{tester := Tester,
+ chunks := Chunks} = State) ->
+ Data = lists:flatten(lists:reverse(Chunks)),
+ ?SEV_ANNOUNCE_READY(Tester, recv_many_small, Data),
+ {ok, maps:remove(chunks, State)}
+ end},
+
+ #{desc => "await continue (recv-one-big)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_CONTINUE(Tester, tester, recv_one_big) of
+ {ok, Size} ->
+ {ok, State#{size => Size}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "recv (one big)",
+ cmd => fun(#{tester := Tester, csock := Sock, size := Size} = _State) ->
+ %% socket:setopt(Sock, otp, debug, true),
+ case socket:recv(Sock, Size) of
+ {ok, Data} ->
+ ?SEV_ANNOUNCE_READY(Tester,
+ recv_one_big,
+ b2l(Data)),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close connection socket (just in case)",
+ cmd => fun(#{csock := Sock} = State) ->
+ (catch socket:close(Sock)),
+ {ok, maps:remove(csock, State)}
+ end},
+ #{desc => "close listen socket",
+ cmd => fun(#{domain := local,
+ lsock := Sock,
+ local_sa := #{path := Path}} = State) ->
+ ok = socket:close(Sock),
+ State1 =
+ unlink_path(Path,
+ fun() ->
+ maps:remove(local_sa, State)
+ end,
+ fun() -> State end),
+ {ok, maps:remove(lsock, State1)};
+ (#{lsock := Sock} = State) ->
+ (catch socket:close(Sock)),
+ {ok, maps:remove(lsock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ ClientSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ {Tester, ServerSA} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ server_sa => ServerSA}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "create node",
+ cmd => fun(#{host := Host} = State) ->
+ case start_node(Host, client) of
+ {ok, Node} ->
+ ?SEV_IPRINT("(remote) client node ~p started",
+ [Node]),
+ {ok, State#{node => Node}};
+ {error, Reason} ->
+ {skip, Reason}
+ end
+ end},
+ #{desc => "monitor client node",
+ cmd => fun(#{node := Node} = _State) ->
+ true = erlang:monitor_node(Node, true),
+ ok
+ end},
+ #{desc => "start remote client",
+ cmd => fun(#{node := Node} = State) ->
+ Pid = traffic_snr_tcp_client_start(Node),
+ ?SEV_IPRINT("client ~p started", [Pid]),
+ {ok, State#{rclient => Pid}}
+ end},
+ #{desc => "monitor remote client",
+ cmd => fun(#{rclient := Pid}) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order remote client to start",
+ cmd => fun(#{rclient := Client,
+ server_sa := ServerSA,
+ proto := Proto}) ->
+ ?SEV_ANNOUNCE_START(Client, {ServerSA, Proto}),
+ ok
+ end},
+ #{desc => "await remote client ready",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, rclient, init,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (connect)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, connect,
+ [{rclient, Client}]),
+ ok
+ end},
+ #{desc => "order remote client to continue (connect)",
+ cmd => fun(#{rclient := Client}) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, connect),
+ ok
+ end},
+ #{desc => "await client process ready (connect)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, rclient, connect,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (connect)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, connect),
+ ok
+ end},
+
+ #{desc => "await continue (send-one-big)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = State) ->
+ case ?SEV_AWAIT_CONTINUE(Tester, tester,
+ send_one_big,
+ [{rclient, Client}]) of
+ {ok, Data} ->
+ {ok, State#{data => Data}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order remote client to continue (send)",
+ cmd => fun(#{rclient := Client, data := Data}) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send, Data),
+ ok
+ end},
+ #{desc => "await client process ready (send)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, rclient, send,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ Result;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (send-one-big)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_one_big),
+ ok
+ end},
+
+ #{desc => "await continue (send-many-small)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = State) ->
+ case ?SEV_AWAIT_CONTINUE(Tester, tester,
+ send_many_small,
+ [{rclient, Client}]) of
+ {ok, Data} ->
+ {ok, State#{data => Data}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order remote client to continue (send chunk 1)",
+ cmd => fun(#{rclient := Client,
+ data := Data} = State) ->
+ {Chunk, RestData} = lists:split(100, Data),
+ %% ?SEV_IPRINT("order send of chunk 1: "
+ %% "~n Size: ~p"
+ %% "~n ~p", [length(Chunk), Chunk]),
+ ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk),
+ {ok, State#{data => RestData}}
+ end},
+ #{desc => "await client process ready (send chunk 1)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, rclient, send,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ Result;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order remote client to continue (send chunk 2)",
+ cmd => fun(#{rclient := Client,
+ data := Data} = State) ->
+ {Chunk, RestData} = lists:split(100, Data),
+ %% ?SEV_IPRINT("order send of chunk 2: "
+ %% "~n Size: ~p"
+ %% "~n ~p", [length(Chunk), Chunk]),
+ ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk),
+ {ok, State#{data => RestData}}
+ end},
+ #{desc => "await client process ready (send chunk 2)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, rclient, send,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ Result;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order remote client to continue (send chunk 3)",
+ cmd => fun(#{rclient := Client,
+ data := Data} = State) ->
+ {Chunk, RestData} = lists:split(100, Data),
+ %% ?SEV_IPRINT("order send of chunk 3: "
+ %% "~n Size: ~p"
+ %% "~n ~p", [length(Chunk), Chunk]),
+ ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk),
+ {ok, State#{data => RestData}}
+ end},
+ #{desc => "await client process ready (send chunk 3)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, rclient, send,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ Result;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order remote client to continue (send chunk 4)",
+ cmd => fun(#{rclient := Client,
+ data := Data} = State) ->
+ {Chunk, RestData} = lists:split(100, Data),
+ %% ?SEV_IPRINT("order send of chunk 4: "
+ %% "~n Size: ~p"
+ %% "~n ~p", [length(Chunk), Chunk]),
+ ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk),
+ {ok, State#{data => RestData}}
+ end},
+ #{desc => "await client process ready (send chunk 4)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, rclient, send,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ Result;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order remote client to continue (send chunk 5)",
+ cmd => fun(#{rclient := Client,
+ data := Data} = State) ->
+ {Chunk, RestData} = lists:split(100, Data),
+ %% ?SEV_IPRINT("order send of chunk 5: "
+ %% "~n Size: ~p"
+ %% "~n ~p", [length(Chunk), Chunk]),
+ ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk),
+ {ok, State#{data => RestData}}
+ end},
+ #{desc => "await client process ready (send chunk 5)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, rclient, send,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ Result;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order remote client to continue (send chunk 6)",
+ cmd => fun(#{rclient := Client,
+ data := Data} = State) ->
+ {Chunk, RestData} = lists:split(100, Data),
+ %% ?SEV_IPRINT("order send of chunk 6: "
+ %% "~n Size: ~p"
+ %% "~n ~p", [length(Chunk), Chunk]),
+ ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk),
+ {ok, State#{data => RestData}}
+ end},
+ #{desc => "await client process ready (send chunk 6)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, rclient, send,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ Result;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order remote client to continue (send chunk 7)",
+ cmd => fun(#{rclient := Client,
+ data := Data} = State) ->
+ {Chunk, RestData} = lists:split(100, Data),
+ %% ?SEV_IPRINT("order send of chunk 7: "
+ %% "~n Size: ~p"
+ %% "~n ~p", [length(Chunk), Chunk]),
+ ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk),
+ {ok, State#{data => RestData}}
+ end},
+ #{desc => "await client process ready (send chunk 7)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, rclient, send,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ Result;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order remote client to continue (send chunk 8)",
+ cmd => fun(#{rclient := Client,
+ data := Data} = State) ->
+ {Chunk, RestData} = lists:split(100, Data),
+ %% ?SEV_IPRINT("order send of chunk 8: "
+ %% "~n Size: ~p"
+ %% "~n ~p", [length(Chunk), Chunk]),
+ ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk),
+ {ok, State#{data => RestData}}
+ end},
+ #{desc => "await client process ready (send chunk 8)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, rclient, send,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ Result;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order remote client to continue (send chunk 9)",
+ cmd => fun(#{rclient := Client,
+ data := Data} = State) ->
+ {Chunk, RestData} = lists:split(100, Data),
+ %% ?SEV_IPRINT("order send of chunk 9: "
+ %% "~n Size: ~p"
+ %% "~n ~p", [length(Chunk), Chunk]),
+ ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk),
+ {ok, State#{data => RestData}}
+ end},
+ #{desc => "await client process ready (send chunk 9)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, rclient, send,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ Result;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order remote client to continue (send chunk 10)",
+ cmd => fun(#{rclient := Client,
+ data := Data} = State) ->
+ {Chunk, []} = lists:split(100, Data),
+ %% ?SEV_IPRINT("order send of chunk 10: "
+ %% "~n Size: ~p"
+ %% "~n ~p", [length(Chunk), Chunk]),
+ ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk),
+ {ok, maps:remove(data, State)}
+ end},
+ #{desc => "await client process ready (send chunk 10)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, rclient, send,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ Result;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order remote client to continue (send stop)",
+ cmd => fun(#{rclient := Client} = State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send, stop),
+ {ok, maps:remove(data, State)}
+ end},
+ #{desc => "await client process ready (send stop)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, rclient, send,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ Result;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (send-many-small)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_many_small),
+ ok
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester,
+ [{rclient, Client}]) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "kill remote client",
+ cmd => fun(#{rclient := Client}) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await remote client termination",
+ cmd => fun(#{rclient := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(rclient, State),
+ {ok, State1}
+ end},
+ #{desc => "stop client node",
+ cmd => fun(#{node := Node} = _State) ->
+ stop_node(Node)
+ end},
+ #{desc => "await client node termination",
+ cmd => fun(#{node := Node} = State) ->
+ receive
+ {nodedown, Node} ->
+ {ok, maps:remove(node, State)}
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = State) ->
+ {ok, ServerSA} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{server_sa => ServerSA}}
+ end},
+
+ %% Start the client
+ #{desc => "order client start",
+ cmd => fun(#{client := Pid,
+ server_sa := ServerSA} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, ServerSA),
+ ok
+ end},
+ #{desc => "await client ready (init)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client, init)
+ end},
+
+ %% The actual test
+ #{desc => "order server continue (accept)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, accept),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order client continue (connect)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, connect),
+ ok
+ end},
+ #{desc => "await server ready (accept)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, accept,
+ [{client, Client}]),
+ ok
+ end},
+ #{desc => "await client ready (connect)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, connect,
+ [{server, Server}])
+ end},
+
+ #{desc => "generate data",
+ cmd => fun(State) ->
+ D1 = lists:seq(1,250),
+ D2 = lists:duplicate(4, D1),
+ D3 = lists:flatten(D2),
+ {ok, State#{data => D3}}
+ end},
+
+ %% (client) Send one big and (server) recv may small
+ #{desc => "order server continue (recv-many-small)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv_many_small),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order client continue (send-one-big)",
+ cmd => fun(#{client := Pid, data := Data} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, send_one_big, Data),
+ ok
+ end},
+ #{desc => "await client ready (send-one-big)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ ok = ?SEV_AWAIT_READY(Client, client, send_one_big,
+ [{server, Server}])
+ end},
+ #{desc => "await server ready (recv-many-small)",
+ cmd => fun(#{server := Server,
+ client := Client,
+ data := Data} = _State) ->
+ case ?SEV_AWAIT_READY(Server, server, recv_many_small,
+ [{client, Client}]) of
+ {ok, Data} ->
+ ok;
+ {ok, OtherData} ->
+ {error, {mismatched_data, Data, OtherData}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "order server continue (recv-one-big)",
+ cmd => fun(#{server := Pid, data := Data} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv_one_big, length(Data)),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order client continue (send-many-small)",
+ cmd => fun(#{client := Pid, data := Data} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, send_many_small, Data),
+ ok
+ end},
+ #{desc => "await client ready (send-many-small)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ ok = ?SEV_AWAIT_READY(Client, client, send_many_small,
+ [{server, Server}])
+ end},
+ #{desc => "await server ready (recv-one-big)",
+ cmd => fun(#{server := Server,
+ client := Client,
+ data := Data} = State) ->
+ case ?SEV_AWAIT_READY(Server, server, recv_one_big,
+ [{client, Client}]) of
+ {ok, Data} ->
+ {ok, maps:remove(data, State)};
+ {ok, OtherData} ->
+ {error, {mismatched_data, Data, OtherData}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% Terminations
+ #{desc => "order client to terminate",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await client termination",
+ cmd => fun(#{client := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(client, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(server, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start server evaluator"),
+ ServerInitState = InitState,
+ Server = ?SEV_START("server", ServerSeq, ServerInitState),
+
+ i("start client evaluator(s)"),
+ ClientInitState = InitState#{host => local_host()},
+ Client = ?SEV_START("client", ClientSeq, ClientInitState),
+
+ i("start 'tester' evaluator"),
+ TesterInitState = #{server => Server#ev.pid,
+ client => Client#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]).
+
+
+
+traffic_snr_tcp_client_start(Node) ->
+ Self = self(),
+ Fun = fun() -> traffic_snr_tcp_client(Self) end,
+ erlang:spawn(Node, Fun).
+
+traffic_snr_tcp_client(Parent) ->
+ {Sock, ServerSA, Path} = traffic_snr_tcp_client_init(Parent),
+ traffic_snr_tcp_client_announce_ready(Parent, init),
+ traffic_snr_tcp_client_await_continue(Parent, connect),
+ traffic_snr_tcp_client_connect(Sock, ServerSA),
+ traffic_snr_tcp_client_announce_ready(Parent, connect),
+ traffic_snr_tcp_client_send_loop(Parent, Sock),
+ Reason = traffic_snr_tcp_client_await_terminate(Parent),
+ traffic_snr_tcp_client_close(Sock, Path),
+ exit(Reason).
+
+
+traffic_snr_tcp_client_send_loop(Parent, Sock) ->
+ case ?SEV_AWAIT_CONTINUE(Parent, parent, send) of
+ {ok, stop} -> % Breakes the loop
+ ?SEV_ANNOUNCE_READY(Parent, send, ok),
+ ok;
+ {ok, Data} ->
+ case socket:send(Sock, Data) of
+ ok ->
+ ?SEV_ANNOUNCE_READY(Parent, send, ok),
+ traffic_snr_tcp_client_send_loop(Parent, Sock);
+ {error, Reason} = ERROR ->
+ ?SEV_ANNOUNCE_READY(Parent, send, ERROR),
+ exit({send, Reason})
+ end;
+ {error, Reason} ->
+ exit({await_continue, Reason})
+ end.
+
+traffic_snr_tcp_client_init(Parent) ->
+ put(sname, "rclient"),
+ ?SEV_IPRINT("init"),
+ _MRef = erlang:monitor(process, Parent),
+ {ServerSA, Proto} = traffic_snr_tcp_client_await_start(Parent),
+ Domain = maps:get(family, ServerSA),
+ Sock = traffic_snr_tcp_client_create(Domain, Proto),
+ Path = traffic_snr_tcp_client_bind(Sock, Domain),
+ {Sock, ServerSA, Path}.
+
+traffic_snr_tcp_client_await_start(Parent) ->
+ i("traffic_snr_tcp_client_await_start -> entry"),
+ ?SEV_AWAIT_START(Parent).
+
+traffic_snr_tcp_client_create(Domain, Proto) ->
+ i("traffic_snr_tcp_client_create -> entry"),
+ case socket:open(Domain, stream, Proto) of
+ {ok, Sock} ->
+ Sock;
+ {error, Reason} ->
+ exit({open_failed, Reason})
+ end.
+
+traffic_snr_tcp_client_bind(Sock, Domain) ->
+ i("traffic_snr_tcp_client_bind -> entry"),
+ LSA = which_local_socket_addr(Domain),
+ case socket:bind(Sock, LSA) of
+ {ok, _} ->
+ case socket:sockname(Sock) of
+ {ok, #{family := local, path := Path}} ->
+ Path;
+ {ok, _} ->
+ undefined;
+ {error, Reason1} ->
+ exit({sockname, Reason1})
+ end;
+ {error, Reason} ->
+ exit({bind, Reason})
+ end.
+
+traffic_snr_tcp_client_announce_ready(Parent, Slogan) ->
+ ?SEV_ANNOUNCE_READY(Parent, Slogan).
+
+traffic_snr_tcp_client_await_continue(Parent, Slogan) ->
+ i("traffic_snr_tcp_client_await_continue -> entry"),
+ ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan).
+
+traffic_snr_tcp_client_connect(Sock, ServerSA) ->
+ i("traffic_snr_tcp_client_connect -> entry"),
+ case socket:connect(Sock, ServerSA) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ exit({connect, Reason})
+ end.
+
+traffic_snr_tcp_client_close(Sock, Path) ->
+ i("traffic_snr_tcp_client_close -> entry"),
+ case socket:close(Sock) of
+ ok ->
+ unlink_path(Path),
+ ok;
+ {error, Reason} ->
+ ?SEV_EPRINT("failed closing: "
+ "~n Reason: ~p", [Reason]),
+ unlink_path(Path),
+ {error, {close, Reason}}
+ end.
+
+traffic_snr_tcp_client_await_terminate(Parent) ->
+ i("traffic_snr_tcp_client_await_terminate -> entry"),
+ case ?SEV_AWAIT_TERMINATE(Parent, parent) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ Reason
+ end.
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the send and recv functions
+%% by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes), medium (8K) and large (8M).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'small' message test case, for IPv4.
+
+traffic_ping_pong_small_send_and_recv_tcp4(suite) ->
+ [];
+traffic_ping_pong_small_send_and_recv_tcp4(doc) ->
+ [];
+traffic_ping_pong_small_send_and_recv_tcp4(Config) when is_list(Config) ->
+ ?TT(?SECS(15)),
+ Msg = l2b(?TPP_SMALL),
+ Num = ?TPP_NUM(Config, ?TPP_SMALL_NUM),
+ tc_try(traffic_ping_pong_small_send_and_recv_tcp4,
+ fun() ->
+ InitState = #{domain => inet,
+ proto => tcp,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_send_and_recv_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the send and recv functions
+%% by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes), medium (8K) and large (8M).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'small' message test case, for IPv6.
+
+traffic_ping_pong_small_send_and_recv_tcp6(suite) ->
+ [];
+traffic_ping_pong_small_send_and_recv_tcp6(doc) ->
+ [];
+traffic_ping_pong_small_send_and_recv_tcp6(Config) when is_list(Config) ->
+ ?TT(?SECS(15)),
+ Msg = l2b(?TPP_SMALL),
+ Num = ?TPP_NUM(Config, ?TPP_SMALL_NUM),
+ tc_try(traffic_ping_pong_small_send_and_recv_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet6,
+ proto => tcp,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_send_and_recv_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the send and recv functions
+%% by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes), medium (8K) and large (8M).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'small' message test case, for Unix Domain (stream) socket.
+
+traffic_ping_pong_small_send_and_recv_tcpL(suite) ->
+ [];
+traffic_ping_pong_small_send_and_recv_tcpL(doc) ->
+ [];
+traffic_ping_pong_small_send_and_recv_tcpL(Config) when is_list(Config) ->
+ ?TT(?SECS(15)),
+ Msg = l2b(?TPP_SMALL),
+ Num = ?TPP_NUM(Config, ?TPP_SMALL_NUM),
+ tc_try(traffic_ping_pong_small_send_and_recv_tcpL,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ InitState = #{domain => local,
+ proto => default,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_send_and_recv_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the send and recv functions
+%% by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes), medium (8K) and large (8M).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'medium' message test case, for IPv4.
+
+traffic_ping_pong_medium_send_and_recv_tcp4(suite) ->
+ [];
+traffic_ping_pong_medium_send_and_recv_tcp4(doc) ->
+ [];
+traffic_ping_pong_medium_send_and_recv_tcp4(Config) when is_list(Config) ->
+ Msg = l2b(?TPP_MEDIUM),
+ Num = ?TPP_NUM(Config, ?TPP_MEDIUM_NUM),
+ tc_try(traffic_ping_pong_medium_send_and_recv_tcp4,
+ fun() ->
+ ?TT(?SECS(30)),
+ InitState = #{domain => inet,
+ proto => tcp,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_send_and_recv_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the send and recv functions
+%% by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes), medium (8K) and large (8M).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'medium' message test case, for IPv6.
+
+traffic_ping_pong_medium_send_and_recv_tcp6(suite) ->
+ [];
+traffic_ping_pong_medium_send_and_recv_tcp6(doc) ->
+ [];
+traffic_ping_pong_medium_send_and_recv_tcp6(Config) when is_list(Config) ->
+ Msg = l2b(?TPP_MEDIUM),
+ Num = ?TPP_NUM(Config, ?TPP_MEDIUM_NUM),
+ tc_try(traffic_ping_pong_medium_send_and_recv_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ ?TT(?SECS(30)),
+ InitState = #{domain => inet6,
+ proto => tcp,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_send_and_recv_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the send and recv functions
+%% by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes), medium (8K) and large (8M).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'medium' message test case, for Unix Domain (stream) socket.
+
+traffic_ping_pong_medium_send_and_recv_tcpL(suite) ->
+ [];
+traffic_ping_pong_medium_send_and_recv_tcpL(doc) ->
+ [];
+traffic_ping_pong_medium_send_and_recv_tcpL(Config) when is_list(Config) ->
+ ?TT(?SECS(30)),
+ Msg = l2b(?TPP_MEDIUM),
+ Num = ?TPP_NUM(Config, ?TPP_MEDIUM_NUM),
+ tc_try(traffic_ping_pong_medium_send_and_recv_tcpL,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ InitState = #{domain => local,
+ proto => default,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_send_and_recv_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the send and recv functions
+%% by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes), medium (8K) and large (8M).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'large' message test case, for IPv4.
+
+traffic_ping_pong_large_send_and_recv_tcp4(suite) ->
+ [];
+traffic_ping_pong_large_send_and_recv_tcp4(doc) ->
+ [];
+traffic_ping_pong_large_send_and_recv_tcp4(Config) when is_list(Config) ->
+ ?TT(?SECS(60)),
+ Msg = l2b(?TPP_LARGE),
+ Num = ?TPP_NUM(Config, ?TPP_LARGE_NUM),
+ tc_try(traffic_ping_pong_large_send_and_recv_tcp4,
+ fun() -> is_old_fedora16() end,
+ fun() ->
+ InitState = #{domain => inet,
+ proto => tcp,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_send_and_recv_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the send and recv functions
+%% by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes), medium (8K) and large (8M).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'large' message test case, for IPv6.
+
+traffic_ping_pong_large_send_and_recv_tcp6(suite) ->
+ [];
+traffic_ping_pong_large_send_and_recv_tcp6(doc) ->
+ [];
+traffic_ping_pong_large_send_and_recv_tcp6(Config) when is_list(Config) ->
+ ?TT(?SECS(60)),
+ Msg = l2b(?TPP_LARGE),
+ Num = ?TPP_NUM(Config, ?TPP_LARGE_NUM),
+ tc_try(traffic_ping_pong_large_send_and_recv_tcp6,
+ fun() -> is_old_fedora16(),
+ has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet6,
+ proto => tcp,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_send_and_recv_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the send and recv functions
+%% by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes), medium (8K) and large (8M).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'large' message test case, for UNix Domain (stream) socket.
+
+traffic_ping_pong_large_send_and_recv_tcpL(suite) ->
+ [];
+traffic_ping_pong_large_send_and_recv_tcpL(doc) ->
+ [];
+traffic_ping_pong_large_send_and_recv_tcpL(Config) when is_list(Config) ->
+ ?TT(?SECS(60)),
+ Msg = l2b(?TPP_LARGE),
+ Num = ?TPP_NUM(Config, ?TPP_LARGE_NUM),
+ tc_try(traffic_ping_pong_large_send_and_recv_tcpL,
+ fun() ->
+ has_support_unix_domain_socket(),
+ traffic_ping_pong_large_host_cond()
+ end,
+ fun() ->
+ InitState = #{domain => local,
+ proto => default,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_send_and_recv_tcp(InitState)
+ end).
+
+%% This test case is a bit extreme and fails on some hosts
+%% (e.g. OpenIndiana Hipster), so exclude them.
+traffic_ping_pong_large_host_cond() ->
+ traffic_ping_pong_large_host_cond(os:type(), os:version()).
+
+traffic_ping_pong_large_host_cond({unix, sunos}, _) ->
+ skip("TC does not work on platform");
+traffic_ping_pong_large_host_cond({unix, linux}, _) ->
+ traffic_ping_pong_large_host_cond2(string:trim(os:cmd("cat /etc/issue")));
+traffic_ping_pong_large_host_cond(_, _) ->
+ ok.
+
+traffic_ping_pong_large_host_cond2("Welcome to SUSE Linux Enterprise Server 10 SP1 (i586)" ++ _) ->
+ skip("TC does not work on platform");
+traffic_ping_pong_large_host_cond2("Fedora release 16 " ++ _) ->
+ skip("Very slow VM");
+traffic_ping_pong_large_host_cond2(_) ->
+ ok.
+
+
+is_old_fedora16() ->
+ is_old_fedora16(string:trim(os:cmd("cat /etc/issue"))).
+
+%% We actually only have one host running this, a slow VM.
+is_old_fedora16("Fedora release 16 " ++ _) ->
+ skip("Very slow VM");
+is_old_fedora16(_) ->
+ ok.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendto and recvfrom
+%% functions by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for two different message sizes;
+%% small (8 bytes) and medium (8K).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'small' message test case, for IPv4.
+
+traffic_ping_pong_small_sendto_and_recvfrom_udp4(suite) ->
+ [];
+traffic_ping_pong_small_sendto_and_recvfrom_udp4(doc) ->
+ [];
+traffic_ping_pong_small_sendto_and_recvfrom_udp4(Config) when is_list(Config) ->
+ Msg = l2b(?TPP_SMALL),
+ Num = ?TPP_NUM(Config, ?TPP_SMALL_NUM),
+ tc_try(traffic_ping_pong_small_sendto_and_recvfrom_udp4,
+ fun() ->
+ ?TT(?SECS(45)),
+ InitState = #{domain => inet,
+ proto => udp,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendto_and_recvfrom_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendto and recvfrom
+%% functions by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for two different message sizes;
+%% small (8 bytes) and medium (8K).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'small' message test case, for IPv6.
+
+traffic_ping_pong_small_sendto_and_recvfrom_udp6(suite) ->
+ [];
+traffic_ping_pong_small_sendto_and_recvfrom_udp6(doc) ->
+ [];
+traffic_ping_pong_small_sendto_and_recvfrom_udp6(Config) when is_list(Config) ->
+ Msg = l2b(?TPP_SMALL),
+ Num = ?TPP_NUM(Config, ?TPP_SMALL_NUM),
+ tc_try(traffic_ping_pong_small_sendto_and_recvfrom_udp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ ?TT(?SECS(45)),
+ InitState = #{domain => inet6,
+ proto => udp,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendto_and_recvfrom_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendto and recvfrom
+%% functions by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for two different message sizes;
+%% small (8 bytes) and medium (8K).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'small' message test case, for Unix Domain (dgram) socket.
+
+traffic_ping_pong_small_sendto_and_recvfrom_udpL(suite) ->
+ [];
+traffic_ping_pong_small_sendto_and_recvfrom_udpL(doc) ->
+ [];
+traffic_ping_pong_small_sendto_and_recvfrom_udpL(Config) when is_list(Config) ->
+ Msg = l2b(?TPP_SMALL),
+ Num = ?TPP_NUM(Config, ?TPP_SMALL_NUM),
+ tc_try(traffic_ping_pong_small_sendto_and_recvfrom_udpL,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ ?TT(?SECS(45)),
+ InitState = #{domain => local,
+ proto => default,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendto_and_recvfrom_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendto and recvfrom
+%% functions by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for two different message sizes;
+%% small (8 bytes) and medium (8K).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'medium' message test case, for IPv4.
+
+traffic_ping_pong_medium_sendto_and_recvfrom_udp4(suite) ->
+ [];
+traffic_ping_pong_medium_sendto_and_recvfrom_udp4(doc) ->
+ [];
+traffic_ping_pong_medium_sendto_and_recvfrom_udp4(Config) when is_list(Config) ->
+ Msg = l2b(?TPP_MEDIUM),
+ Num = ?TPP_NUM(Config, ?TPP_MEDIUM_NUM),
+ tc_try(traffic_ping_pong_medium_sendto_and_recvfrom_udp4,
+ fun() ->
+ ?TT(?SECS(45)),
+ InitState = #{domain => inet,
+ proto => udp,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendto_and_recvfrom_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendto and recvfrom
+%% functions by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for two different message sizes;
+%% small (8 bytes) and medium (8K).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'medium' message test case, for IPv6.
+
+traffic_ping_pong_medium_sendto_and_recvfrom_udp6(suite) ->
+ [];
+traffic_ping_pong_medium_sendto_and_recvfrom_udp6(doc) ->
+ [];
+traffic_ping_pong_medium_sendto_and_recvfrom_udp6(Config) when is_list(Config) ->
+ Msg = l2b(?TPP_MEDIUM),
+ Num = ?TPP_NUM(Config, ?TPP_MEDIUM_NUM),
+ tc_try(traffic_ping_pong_medium_sendto_and_recvfrom_udp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ ?TT(?SECS(45)),
+ InitState = #{domain => inet6,
+ proto => udp,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendto_and_recvfrom_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendto and recvfrom
+%% functions by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for two different message sizes;
+%% small (8 bytes) and medium (8K).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'medium' message test case, for Unix Domain (dgram) socket.
+
+traffic_ping_pong_medium_sendto_and_recvfrom_udpL(suite) ->
+ [];
+traffic_ping_pong_medium_sendto_and_recvfrom_udpL(doc) ->
+ [];
+traffic_ping_pong_medium_sendto_and_recvfrom_udpL(Config) when is_list(Config) ->
+ Msg = l2b(?TPP_MEDIUM),
+ Num = ?TPP_NUM(Config, ?TPP_MEDIUM_NUM),
+ tc_try(traffic_ping_pong_medium_sendto_and_recvfrom_udpL,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ ?TT(?SECS(45)),
+ InitState = #{domain => local,
+ proto => default,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendto_and_recvfrom_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendmsg and recvmsg
+%% functions by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes), medium (8K) and large (8M).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'small' message test case, for IPv4.
+
+traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4(suite) ->
+ [];
+traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4(doc) ->
+ [];
+traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4(Config) when is_list(Config) ->
+ Msg = l2b(?TPP_SMALL),
+ Num = ?TPP_NUM(Config, ?TPP_SMALL_NUM),
+ tc_try(traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4,
+ fun() ->
+ ?TT(?SECS(20)),
+ InitState = #{domain => inet,
+ proto => tcp,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendmsg and recvmsg functions
+%% by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes), medium (8K) and large (8M).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'small' message test case, for IPv6.
+
+traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6(suite) ->
+ [];
+traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6(doc) ->
+ [];
+traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6(Config) when is_list(Config) ->
+ Msg = l2b(?TPP_SMALL),
+ Num = ?TPP_NUM(Config, ?TPP_SMALL_NUM),
+ tc_try(traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ ?TT(?SECS(20)),
+ InitState = #{domain => inet6,
+ proto => tcp,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendmsg and recvmsg functions
+%% by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes), medium (8K) and large (8M).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'small' message test case, for Unix Domain (stream) socket.
+
+traffic_ping_pong_small_sendmsg_and_recvmsg_tcpL(suite) ->
+ [];
+traffic_ping_pong_small_sendmsg_and_recvmsg_tcpL(doc) ->
+ [];
+traffic_ping_pong_small_sendmsg_and_recvmsg_tcpL(Config) when is_list(Config) ->
+ Msg = l2b(?TPP_SMALL),
+ Num = ?TPP_NUM(Config, ?TPP_SMALL_NUM),
+ tc_try(traffic_ping_pong_small_sendmsg_and_recvmsg_tcpL,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ ?TT(?SECS(20)),
+ InitState = #{domain => local,
+ proto => default,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendmsg and recvmsg
+%% functions by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes), medium (8K) and large (8M).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'medium' message test case, for IPv4.
+
+traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4(suite) ->
+ [];
+traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4(doc) ->
+ [];
+traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4(Config) when is_list(Config) ->
+ Msg = l2b(?TPP_MEDIUM),
+ Num = ?TPP_NUM(Config, ?TPP_MEDIUM_NUM),
+ tc_try(traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4,
+ fun() ->
+ ?TT(?SECS(30)),
+ InitState = #{domain => inet,
+ proto => tcp,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendmsg and recvmsg functions
+%% by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes), medium (8K) and large (8M).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'medium' message test case, for IPv6.
+
+traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6(suite) ->
+ [];
+traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6(doc) ->
+ [];
+traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6(Config) when is_list(Config) ->
+ Msg = l2b(?TPP_MEDIUM),
+ Num = ?TPP_NUM(Config, ?TPP_MEDIUM_NUM),
+ tc_try(traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ ?TT(?SECS(30)),
+ InitState = #{domain => inet6,
+ proto => tcp,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendmsg and recvmsg functions
+%% by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes), medium (8K) and large (8M).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'medium' message test case, for Unix Domain (stream) socket.
+
+traffic_ping_pong_medium_sendmsg_and_recvmsg_tcpL(suite) ->
+ [];
+traffic_ping_pong_medium_sendmsg_and_recvmsg_tcpL(doc) ->
+ [];
+traffic_ping_pong_medium_sendmsg_and_recvmsg_tcpL(Config) when is_list(Config) ->
+ Msg = l2b(?TPP_MEDIUM),
+ Num = ?TPP_NUM(Config, ?TPP_MEDIUM_NUM),
+ tc_try(traffic_ping_pong_medium_sendmsg_and_recvmsg_tcpL,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ ?TT(?SECS(30)),
+ InitState = #{domain => local,
+ proto => default,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendmsg and recvmsg
+%% functions by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes), medium (8K) and large (8M).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'large' message test case, for IPv4.
+
+traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4(suite) ->
+ [];
+traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4(doc) ->
+ [];
+traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4(Config) when is_list(Config) ->
+ Msg = l2b(?TPP_LARGE),
+ Num = ?TPP_NUM(Config, ?TPP_LARGE_NUM),
+ tc_try(traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4,
+ fun() -> traffic_ping_pong_large_sendmsg_and_recvmsg_cond() end,
+ fun() ->
+ ?TT(?SECS(60)),
+ InitState = #{domain => inet,
+ proto => tcp,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState)
+ end).
+
+
+traffic_ping_pong_large_sendmsg_and_recvmsg_cond() ->
+ traffic_ping_pong_large_sendmsg_and_recvmsg_cond(os:type(), os:version()).
+
+traffic_ping_pong_large_sendmsg_and_recvmsg_cond({unix, linux}, {M, _, _})
+ when (M < 3) ->
+ skip("TC may not work on this version");
+traffic_ping_pong_large_sendmsg_and_recvmsg_cond(_, _) ->
+ ok.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendmsg and recvmsg functions
+%% by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes), medium (8K) and large (8M).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'large' message test case, for IPv6.
+
+traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6(suite) ->
+ [];
+traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6(doc) ->
+ [];
+traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6(Config) when is_list(Config) ->
+ Msg = l2b(?TPP_LARGE),
+ Num = ?TPP_NUM(Config, ?TPP_LARGE_NUM),
+ tc_try(traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6,
+ fun() ->
+ has_support_ipv6(),
+ traffic_ping_pong_large_sendmsg_and_recvmsg_cond()
+ end,
+ fun() ->
+ ?TT(?SECS(60)),
+ InitState = #{domain => inet6,
+ proto => tcp,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendmsg and recvmsg functions
+%% by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes), medium (8K) and large (8M).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'large' message test case, for Unix Domain (stream) socket.
+
+traffic_ping_pong_large_sendmsg_and_recvmsg_tcpL(suite) ->
+ [];
+traffic_ping_pong_large_sendmsg_and_recvmsg_tcpL(doc) ->
+ [];
+traffic_ping_pong_large_sendmsg_and_recvmsg_tcpL(Config) when is_list(Config) ->
+ Msg = l2b(?TPP_LARGE),
+ Num = ?TPP_NUM(Config, ?TPP_LARGE_NUM),
+ tc_try(traffic_ping_pong_large_sendmsg_and_recvmsg_tcpL,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ ?TT(?SECS(60)),
+ InitState = #{domain => local,
+ proto => default,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendmsg and recvmsg
+%% functions by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes) and medium (8K).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'small' message test case, for IPv4.
+
+traffic_ping_pong_small_sendmsg_and_recvmsg_udp4(suite) ->
+ [];
+traffic_ping_pong_small_sendmsg_and_recvmsg_udp4(doc) ->
+ [];
+traffic_ping_pong_small_sendmsg_and_recvmsg_udp4(Config) when is_list(Config) ->
+ Msg = l2b(?TPP_SMALL),
+ Num = ?TPP_NUM(Config, ?TPP_SMALL_NUM),
+ tc_try(traffic_ping_pong_small_sendmsg_and_recvmsg_udp4,
+ fun() ->
+ ?TT(?SECS(60)),
+ InitState = #{domain => inet,
+ proto => udp,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendmsg_and_recvmsg_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendmsg and recvmsg functions
+%% by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes) and medium (8K).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'small' message test case, for IPv6.
+
+traffic_ping_pong_small_sendmsg_and_recvmsg_udp6(suite) ->
+ [];
+traffic_ping_pong_small_sendmsg_and_recvmsg_udp6(doc) ->
+ [];
+traffic_ping_pong_small_sendmsg_and_recvmsg_udp6(Config) when is_list(Config) ->
+ Msg = l2b(?TPP_SMALL),
+ Num = ?TPP_NUM(Config, ?TPP_SMALL_NUM),
+ tc_try(traffic_ping_pong_small_sendmsg_and_recvmsg_udp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ ?TT(?SECS(60)),
+ InitState = #{domain => inet6,
+ proto => udp,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendmsg_and_recvmsg_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendmsg and recvmsg functions
+%% by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes) and medium (8K).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'small' message test case, for Unix Domain (dgram) socket.
+
+traffic_ping_pong_small_sendmsg_and_recvmsg_udpL(suite) ->
+ [];
+traffic_ping_pong_small_sendmsg_and_recvmsg_udpL(doc) ->
+ [];
+traffic_ping_pong_small_sendmsg_and_recvmsg_udpL(Config) when is_list(Config) ->
+ Msg = l2b(?TPP_SMALL),
+ Num = ?TPP_NUM(Config, ?TPP_SMALL_NUM),
+ tc_try(traffic_ping_pong_small_sendmsg_and_recvmsg_udpL,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ ?TT(?SECS(60)),
+ InitState = #{domain => local,
+ proto => default,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendmsg_and_recvmsg_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendmsg and recvmsg
+%% functions by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes) and medium (8K).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'medium' message test case, for IPv4.
+
+traffic_ping_pong_medium_sendmsg_and_recvmsg_udp4(suite) ->
+ [];
+traffic_ping_pong_medium_sendmsg_and_recvmsg_udp4(doc) ->
+ [];
+traffic_ping_pong_medium_sendmsg_and_recvmsg_udp4(Config) when is_list(Config) ->
+ Msg = l2b(?TPP_MEDIUM),
+ Num = ?TPP_NUM(Config, ?TPP_MEDIUM_NUM),
+ tc_try(traffic_ping_pong_medium_sendmsg_and_recvmsg_udp4,
+ fun() ->
+ ?TT(?SECS(60)),
+ InitState = #{domain => inet,
+ proto => udp,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendmsg_and_recvmsg_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendmsg and recvmsg
+%% functions by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes) and medium (8K).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'medium' message test case, for IPv6.
+
+traffic_ping_pong_medium_sendmsg_and_recvmsg_udp6(suite) ->
+ [];
+traffic_ping_pong_medium_sendmsg_and_recvmsg_udp6(doc) ->
+ [];
+traffic_ping_pong_medium_sendmsg_and_recvmsg_udp6(Config) when is_list(Config) ->
+ Msg = l2b(?TPP_MEDIUM),
+ Num = ?TPP_NUM(Config, ?TPP_MEDIUM_NUM),
+ tc_try(traffic_ping_pong_medium_sendmsg_and_recvmsg_udp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ ?TT(?SECS(60)),
+ InitState = #{domain => inet6,
+ proto => udp,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendmsg_and_recvmsg_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendmsg and recvmsg
+%% functions by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes) and medium (8K).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'medium' message test case, for Unix Domain (dgram) socket.
+
+traffic_ping_pong_medium_sendmsg_and_recvmsg_udpL(suite) ->
+ [];
+traffic_ping_pong_medium_sendmsg_and_recvmsg_udpL(doc) ->
+ [];
+traffic_ping_pong_medium_sendmsg_and_recvmsg_udpL(Config) when is_list(Config) ->
+ Msg = l2b(?TPP_MEDIUM),
+ Num = ?TPP_NUM(Config, ?TPP_MEDIUM_NUM),
+ tc_try(traffic_ping_pong_medium_sendmsg_and_recvmsg_udpL,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ ?TT(?SECS(60)),
+ InitState = #{domain => local,
+ proto => default,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendmsg_and_recvmsg_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Ping-Pong for TCP
+
+traffic_ping_pong_send_and_recv_tcp(InitState) ->
+ Send = fun(Sock, Data) -> socket:send(Sock, Data) end,
+ Recv = fun(Sock, Sz) -> socket:recv(Sock, Sz) end,
+ InitState2 = InitState#{send => Send, % Send function
+ recv => Recv % Receive function
+ },
+ traffic_ping_pong_send_and_receive_tcp(InitState2).
+
+traffic_ping_pong_sendmsg_and_recvmsg_tcp(#{domain := local} = InitState) ->
+ Recv = fun(Sock, Sz) ->
+ case socket:recvmsg(Sock, Sz, 0) of
+ %% On some platforms, the address
+ %% is *not* provided (e.g. FreeBSD)
+ {ok, #{addr := undefined,
+ iov := [Data]}} ->
+ {ok, Data};
+ %% On some platforms, the address
+ %% *is* provided (e.g. linux)
+ {ok, #{addr := #{family := local},
+ iov := [Data]}} ->
+ {ok, Data};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState2 = InitState#{recv => Recv}, % Receive function
+ traffic_ping_pong_sendmsg_and_recvmsg_tcp2(InitState2);
+traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) ->
+ Recv = fun(Sock, Sz) ->
+ case socket:recvmsg(Sock, Sz, 0) of
+ {ok, #{addr := undefined,
+ iov := [Data]}} ->
+ {ok, Data};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState2 = InitState#{recv => Recv}, % Receive function
+ traffic_ping_pong_sendmsg_and_recvmsg_tcp2(InitState2).
+
+traffic_ping_pong_sendmsg_and_recvmsg_tcp2(InitState) ->
+ Send = fun(Sock, Data) when is_binary(Data) ->
+ MsgHdr = #{iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr);
+ (Sock, Data) when is_list(Data) -> %% We assume iovec...
+ MsgHdr = #{iov => Data},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ InitState2 = InitState#{send => Send}, % Send function
+ traffic_ping_pong_send_and_receive_tcp(InitState2).
+
+
+traffic_ping_pong_send_and_receive_tcp(#{msg := Msg} = InitState) ->
+ Fun = fun(Sock) ->
+ {ok, RcvSz} = socket:getopt(Sock, socket, rcvbuf),
+ ?SEV_IPRINT("RcvBuf is ~p (needs atleast ~p)",
+ [RcvSz, 16+size(Msg)]),
+ if (RcvSz < size(Msg)) ->
+ NewRcvSz = 1024+size(Msg),
+ case socket:setopt(Sock, socket, rcvbuf, NewRcvSz) of
+ ok ->
+ ok;
+ {error, enobufs} ->
+ skip(?F("Change ~w buffer size (to ~w) "
+ "not allowed",
+ [rcvbuf, NewRcvSz]));
+ {error, Reason1} ->
+ ?FAIL({rcvbuf, Reason1})
+ end;
+ true ->
+ ok
+ end,
+ {ok, SndSz} = socket:getopt(Sock, socket, sndbuf),
+ ?SEV_IPRINT("SndBuf is ~p (needs atleast ~p)",
+ [SndSz, 16+size(Msg)]),
+ if (SndSz < size(Msg)) ->
+ NewSndSz = 1024+size(Msg),
+ case socket:setopt(Sock, socket, sndbuf, NewSndSz) of
+ ok ->
+ ok;
+ {error, enobufs} ->
+ skip(?F("Change ~w buffer size (to ~w) "
+ "not allowed",
+ [sndbuf, NewSndSz]));
+ {error, Reason2} ->
+ ?FAIL({sndbuf, Reason2})
+ end;
+ true ->
+ ok
+ end,
+ ok = socket:setopt(Sock, otp, rcvbuf, {12, 1024})
+ end,
+ traffic_ping_pong_send_and_receive_tcp2(InitState#{buf_init => Fun}).
+
+traffic_ping_pong_send_and_receive_tcp2(InitState) ->
+ ServerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "create listen socket",
+ cmd => fun(#{domain := Domain, proto := Proto} = State) ->
+ case socket:open(Domain, stream, Proto) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{domain := local,
+ lsock := LSock,
+ lsa := LSA} = _State) ->
+ case socket:bind(LSock, LSA) of
+ {ok, _Port} ->
+ ok; % We do not care about the port for local
+ {error, _} = ERROR ->
+ ERROR
+ end;
+ (#{lsock := LSock, local_sa := LSA} = State) ->
+ case sock_bind(LSock, LSA) of
+ {ok, Port} ->
+ ?SEV_IPRINT("bound to port: ~w", [Port]),
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "maybe init buffers",
+ cmd => fun(#{lsock := LSock, buf_init := BufInit} = _State) ->
+ BufInit(LSock)
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{domain := local,
+ tester := Tester, local_sa := LSA}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, LSA),
+ ok;
+ (#{tester := Tester, local_sa := LSA, lport := Port}) ->
+ ServerSA = LSA#{port => Port},
+ ?SEV_ANNOUNCE_READY(Tester, init, ServerSA),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "accept",
+ cmd => fun(#{lsock := LSock} = State) ->
+ case socket:accept(LSock) of
+ {ok, Sock} ->
+ {ok, State#{csock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "create handler",
+ cmd => fun(State) ->
+ Handler = tpp_tcp_handler_create(),
+ ?SEV_IPRINT("handler created: ~p", [Handler]),
+ {ok, State#{handler => Handler}}
+ end},
+ #{desc => "monitor handler",
+ cmd => fun(#{handler := Handler} = _State) ->
+ _MRef = erlang:monitor(process, Handler),
+ ok
+ end},
+ #{desc => "transfer connection socket ownership to handler",
+ cmd => fun(#{handler := Handler, csock := Sock} = _State) ->
+ socket:setopt(Sock, otp, controlling_process, Handler)
+ end},
+ #{desc => "start handler",
+ cmd => fun(#{handler := Handler,
+ csock := Sock,
+ send := Send,
+ recv := Recv} = _State) ->
+ ?SEV_ANNOUNCE_START(Handler, {Sock, Send, Recv}),
+ ok
+ end},
+ #{desc => "await handler ready (init)",
+ cmd => fun(#{tester := Tester,
+ handler := Handler} = State) ->
+ case ?SEV_AWAIT_READY(Handler, handler, init,
+ [{tester, Tester}]) of
+ ok ->
+ {ok, maps:remove(csock, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept),
+ ok
+ end},
+ #{desc => "await continue (recv)",
+ cmd => fun(#{tester := Tester,
+ handler := Handler} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv,
+ [{handler, Handler}])
+ end},
+ #{desc => "order handler to recv",
+ cmd => fun(#{handler := Handler} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Handler, recv),
+ ok
+ end},
+ #{desc => "await handler ready (recv)",
+ cmd => fun(#{tester := Tester,
+ handler := Handler} = State) ->
+ case ?SEV_AWAIT_READY(Handler, handler, recv,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ %% ?SEV_IPRINT("Result: ~p", [Result]),
+ {ok, State#{result => Result}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv)",
+ cmd => fun(#{tester := Tester,
+ result := Result} = State) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv, Result),
+ {ok, maps:remove(result, State)}
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "stop handler",
+ cmd => fun(#{handler := Handler}) ->
+ ?SEV_ANNOUNCE_TERMINATE(Handler),
+ ok
+ end},
+ #{desc => "await handler termination",
+ cmd => fun(#{handler := Handler} = State) ->
+ ?SEV_AWAIT_TERMINATION(Handler),
+ State1 = maps:remove(handler, State),
+ {ok, State1}
+ end},
+ #{desc => "close listen socket",
+ cmd => fun(#{domain := local,
+ lsock := Sock,
+ local_sa := #{path := Path}} = State) ->
+ (catch socket:close(Sock)),
+ State1 =
+ unlink_path(Path,
+ fun() ->
+ maps:remove(local_sa, State)
+ end,
+ fun() -> State end),
+ {ok, maps:remove(lsock, State1)};
+ (#{lsock := Sock} = State) ->
+ (catch socket:close(Sock)),
+ {ok, maps:remove(lsock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ ClientSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ {Tester, ServerSA} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ server_sa => ServerSA}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "create node",
+ cmd => fun(#{host := Host} = State) ->
+ case start_node(Host, client) of
+ {ok, Node} ->
+ ?SEV_IPRINT("(remote) client node ~p started",
+ [Node]),
+ {ok, State#{node => Node}};
+ {error, Reason} ->
+ {skip, Reason}
+ end
+ end},
+ #{desc => "monitor client node",
+ cmd => fun(#{node := Node} = _State) ->
+ true = erlang:monitor_node(Node, true),
+ ok
+ end},
+ #{desc => "create remote client",
+ cmd => fun(#{node := Node} = State) ->
+ Pid = tpp_tcp_client_create(Node),
+ ?SEV_IPRINT("remote client created: ~p", [Pid]),
+ {ok, State#{rclient => Pid}}
+ end},
+ #{desc => "monitor remote client",
+ cmd => fun(#{rclient := Pid}) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order remote client to start",
+ cmd => fun(#{rclient := RClient,
+ proto := Proto,
+ server_sa := ServerSA,
+ buf_init := BufInit,
+ send := Send,
+ recv := Recv}) ->
+ ?SEV_ANNOUNCE_START(RClient,
+ {ServerSA, Proto, BufInit,
+ Send, Recv}),
+ ok
+ end},
+ #{desc => "await remote client ready",
+ cmd => fun(#{tester := Tester,
+ rclient := RClient} = _State) ->
+ case ?SEV_AWAIT_READY(RClient, rclient, init,
+ [{tester, Tester}]) of
+ ok ->
+ ?SEV_IPRINT("remote client started"),
+ ok;
+ {error, {unexpected_exit, _, {bind, eaddrnotavail = Reason}}} ->
+ ?SEV_IPRINT("remote client bind failure:"
+ "~n ~p", [Reason]),
+ {skip, Reason};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("remote client failure:"
+ "~n ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (connect)",
+ cmd => fun(#{tester := Tester,
+ rclient := RClient} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, connect,
+ [{rclient, RClient}]),
+ ok
+ end},
+ #{desc => "order remote client to continue (connect)",
+ cmd => fun(#{rclient := RClient}) ->
+ ?SEV_ANNOUNCE_CONTINUE(RClient, connect),
+ ok
+ end},
+ #{desc => "await remote client ready (connect)",
+ cmd => fun(#{tester := Tester,
+ rclient := RClient} = _State) ->
+ ?SEV_AWAIT_READY(RClient, rclient, connect,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (connect)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, connect),
+ ok
+ end},
+ #{desc => "await continue (send)",
+ cmd => fun(#{tester := Tester,
+ rclient := RClient} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester,
+ send,
+ [{rclient, RClient}])
+ end},
+ #{desc => "order remote client to continue (send)",
+ cmd => fun(#{rclient := RClient,
+ msg := Msg,
+ num := Num} = State) ->
+ Data = {Msg, Num},
+ ?SEV_ANNOUNCE_CONTINUE(RClient, send, Data),
+ {ok, maps:remove(data, State)}
+ end},
+ #{desc => "await remote client ready (send)",
+ cmd => fun(#{tester := Tester,
+ rclient := RClient} = State) ->
+ case ?SEV_AWAIT_READY(RClient, rclient, send,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ {ok, State#{result => Result}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (send)",
+ cmd => fun(#{tester := Tester, result := Result} = State) ->
+ ?SEV_ANNOUNCE_READY(Tester, send, Result),
+ {ok, maps:remove(result, State)}
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester,
+ rclient := RClient} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester,
+ [{rclient, RClient}]) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "stop remote client",
+ cmd => fun(#{rclient := RClient}) ->
+ ?SEV_ANNOUNCE_TERMINATE(RClient),
+ ok
+ end},
+ #{desc => "await remote client termination",
+ cmd => fun(#{rclient := RClient} = State) ->
+ ?SEV_AWAIT_TERMINATION(RClient),
+ State1 = maps:remove(rclient, State),
+ {ok, State1}
+ end},
+ #{desc => "stop client node",
+ cmd => fun(#{node := Node} = _State) ->
+ stop_node(Node)
+ end},
+ #{desc => "await client node termination",
+ cmd => fun(#{node := Node} = State) ->
+ receive
+ {nodedown, Node} ->
+ {ok, maps:remove(node, State)}
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = State) ->
+ {ok, ServerSA} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{server_sa => ServerSA}}
+ end},
+
+ %% Start the client
+ #{desc => "order client start",
+ cmd => fun(#{client := Pid,
+ server_sa := ServerSA} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, ServerSA),
+ ok
+ end},
+ #{desc => "await client ready (init)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client, init)
+ end},
+
+ %% The actual test
+ #{desc => "order server continue (accept)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, accept),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order client continue (connect)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, connect),
+ ok
+ end},
+ #{desc => "await server ready (accept)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, accept,
+ [{client, Client}]),
+ ok
+ end},
+ #{desc => "await client ready (connect)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, connect,
+ [{server, Server}])
+ end},
+ #{desc => "order server continue (recv)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order client continue (send)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, send),
+ ok
+ end},
+ #{desc => "await client ready (send)",
+ cmd => fun(#{server := Server,
+ client := Client} = State) ->
+ case ?SEV_AWAIT_READY(Client, client, send,
+ [{server, Server}]) of
+ {ok, {_, _, _, _, _} = Result} ->
+ ?SEV_IPRINT("client result: "
+ "~n ~p", [Result]),
+ {ok, State#{client_result => Result}};
+ {ok, BadResult} ->
+ ?SEV_EPRINT("client result: "
+ "~n ~p", [BadResult]),
+ {error, {invalid_client_result, BadResult}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "await server ready (recv)",
+ cmd => fun(#{server := Server,
+ client := Client,
+ num := Num} = State) ->
+ case ?SEV_AWAIT_READY(Server, server, recv,
+ [{client, Client}]) of
+ {ok, {Num, _, _, _, _} = Result} ->
+ ?SEV_IPRINT("server result: "
+ "~n ~p", [Result]),
+ Result2 = erlang:delete_element(1, Result),
+ {ok, State#{server_result => Result2}};
+ {ok, BadResult} ->
+ ?SEV_EPRINT("bad server result: "
+ "~n ~p", [BadResult]),
+ {error, {invalid_server_result, BadResult}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "present result",
+ cmd => fun(#{server_result := SRes,
+ client_result := CRes,
+ num := Num} = State) ->
+ {SSent, SReceived, SStart, SStop} = SRes,
+ {CSent, CReceived, _, CStart, CStop} = CRes,
+ STime = tdiff(SStart, SStop),
+ CTime = tdiff(CStart, CStop),
+ %% Note that the sizes we are counting is only
+ %% the "data" part of the messages. There is also
+ %% fixed header for each message, which of cource
+ %% is small for the large messages, but comparatively
+ %% big for the small messages!
+ ?SEV_IPRINT("Results: ~w messages exchanged"
+ "~n Server: ~w msec"
+ "~n ~.2f msec/message (roundtrip)"
+ "~n ~.2f messages/msec (roundtrip)"
+ "~n ~w bytes/msec sent"
+ "~n ~w bytes/msec received"
+ "~n Client: ~w msec"
+ "~n ~.2f msec/message (roundtrip)"
+ "~n ~.2f messages/msec (roundtrip)"
+ "~n ~w bytes/msec sent"
+ "~n ~w bytes/msec received",
+ [Num,
+ STime,
+ STime / Num,
+ Num / STime,
+ SSent div STime,
+ SReceived div STime,
+ CTime,
+ CTime / Num,
+ Num / CTime,
+ CSent div CTime,
+ CReceived div CTime]),
+ State1 = maps:remove(server_result, State),
+ State2 = maps:remove(client_result, State1),
+ {ok, State2}
+ end},
+
+ %% Terminations
+ #{desc => "order client to terminate",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await client termination",
+ cmd => fun(#{client := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(client, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(server, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start server evaluator"),
+ ServerInitState = #{domain => maps:get(domain, InitState),
+ proto => maps:get(proto, InitState),
+ recv => maps:get(recv, InitState),
+ send => maps:get(send, InitState),
+ buf_init => maps:get(buf_init, InitState)},
+ Server = ?SEV_START("server", ServerSeq, ServerInitState),
+
+ i("start client evaluator(s)"),
+ ClientInitState = InitState#{host => local_host()},
+ Client = ?SEV_START("client", ClientSeq, ClientInitState),
+
+ i("start 'tester' evaluator"),
+ TesterInitState = #{server => Server#ev.pid,
+ client => Client#ev.pid,
+ num => maps:get(num, InitState)},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]).
+
+
+tpp_tcp_handler_create() ->
+ Self = self(),
+ erlang:spawn(fun() -> tpp_tcp_handler(Self) end).
+
+tpp_tcp_handler(Parent) ->
+ tpp_tcp_handler_init(Parent),
+ {Sock, Send, Recv} = tpp_tcp_handler_await_start(Parent),
+ tpp_tcp_handler_announce_ready(Parent, init),
+ tpp_tcp_handler_await_continue(Parent, recv),
+ Result = tpp_tcp_handler_msg_exchange(Sock, Send, Recv),
+ tpp_tcp_handler_announce_ready(Parent, recv, Result),
+ Reason = tpp_tcp_handler_await_terminate(Parent),
+ ?SEV_IPRINT("terminating"),
+ exit(Reason).
+
+tpp_tcp_handler_init(Parent) ->
+ put(sname, "handler"),
+ ?SEV_IPRINT("init"),
+ _MRef = erlang:monitor(process, Parent),
+ ok.
+
+tpp_tcp_handler_await_start(Parent) ->
+ ?SEV_IPRINT("await start"),
+ ?SEV_AWAIT_START(Parent).
+
+tpp_tcp_handler_announce_ready(Parent, Slogan) ->
+ ?SEV_IPRINT("announce ready (~p)", [Slogan]),
+ ?SEV_ANNOUNCE_READY(Parent, Slogan).
+tpp_tcp_handler_announce_ready(Parent, Slogan, Extra) ->
+ ?SEV_IPRINT("announce ready (~p)", [Slogan]),
+ ?SEV_ANNOUNCE_READY(Parent, Slogan, Extra).
+
+tpp_tcp_handler_await_continue(Parent, Slogan) ->
+ ?SEV_IPRINT("await continue (~p)", [Slogan]),
+ case ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan) of
+ ok ->
+ %% ?SEV_IPRINT("continue (~p): ok", [Slogan]),
+ ok;
+ {error, Reason} ->
+ ?SEV_EPRINT("continue (~p): error"
+ "~n ~p", [Slogan, Reason]),
+ exit({continue, Slogan, Reason})
+ end.
+
+tpp_tcp_handler_await_terminate(Parent) ->
+ ?SEV_IPRINT("await terminate"),
+ case ?SEV_AWAIT_TERMINATE(Parent, parent) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ Reason
+ end.
+
+tpp_tcp_handler_msg_exchange(Sock, Send, Recv) ->
+ tpp_tcp_handler_msg_exchange_loop(Sock, Send, Recv, 0, 0, 0, undefined).
+
+tpp_tcp_handler_msg_exchange_loop(Sock, Send, Recv, N, Sent, Received, Start) ->
+ %% ?SEV_IPRINT("[~w] try receive", [N]),
+ case tpp_tcp_recv_req(Sock, Recv) of
+ {ok, Msg, RecvSz} ->
+ NewStart = if (Start =:= undefined) -> ?LIB:timestamp();
+ true -> Start end,
+ %% ?SEV_IPRINT("[~w] received - now try send", [N]),
+ case tpp_tcp_send_rep(Sock, Send, Msg) of
+ {ok, SendSz} ->
+ tpp_tcp_handler_msg_exchange_loop(Sock, Send, Recv,
+ N+1,
+ Sent+SendSz,
+ Received+RecvSz,
+ NewStart);
+ {error, SReason} ->
+ ?SEV_EPRINT("send (~w): ~p", [N, SReason]),
+ exit({send, SReason, N})
+ end;
+ {error, closed} ->
+ ?SEV_IPRINT("closed - we are done: ~w, ~w, ~w", [N, Sent, Received]),
+ Stop = ?LIB:timestamp(),
+ {N, Sent, Received, Start, Stop};
+ {error, RReason} ->
+ ?SEV_EPRINT("recv (~w): ~p", [N, RReason]),
+ exit({recv, RReason, N})
+ end.
+
+%% The (remote) client process
+
+tpp_tcp_client_create(Node) ->
+ Self = self(),
+ Fun = fun() -> tpp_tcp_client(Self) end,
+ erlang:spawn(Node, Fun).
+
+tpp_tcp_client(Parent) ->
+ tpp_tcp_client_init(Parent),
+ {ServerSA, Proto, BufInit, Send, Recv} = tpp_tcp_client_await_start(Parent),
+ Domain = maps:get(family, ServerSA),
+ Sock = tpp_tcp_client_sock_open(Domain, Proto, BufInit),
+ Path = tpp_tcp_client_sock_bind(Sock, Domain),
+ tpp_tcp_client_announce_ready(Parent, init),
+ tpp_tcp_client_await_continue(Parent, connect),
+ tpp_tcp_client_sock_connect(Sock, ServerSA),
+ tpp_tcp_client_announce_ready(Parent, connect),
+ {InitMsg, Num} = tpp_tcp_client_await_continue(Parent, send),
+ Result = tpp_tcp_client_msg_exchange(Sock, Send, Recv, InitMsg, Num),
+ tpp_tcp_client_announce_ready(Parent, send, Result),
+ Reason = tpp_tcp_client_await_terminate(Parent),
+ tpp_tcp_client_sock_close(Sock, Path),
+ ?SEV_IPRINT("terminating"),
+ exit(Reason).
+
+tpp_tcp_client_init(Parent) ->
+ put(sname, "rclient"),
+ ?SEV_IPRINT("init"),
+ _MRef = erlang:monitor(process, Parent),
+ ok.
+
+tpp_tcp_client_await_start(Parent) ->
+ ?SEV_IPRINT("await start"),
+ ?SEV_AWAIT_START(Parent).
+
+tpp_tcp_client_announce_ready(Parent, Slogan) ->
+ ?SEV_IPRINT("announce ready (~p)", [Slogan]),
+ ?SEV_ANNOUNCE_READY(Parent, Slogan).
+tpp_tcp_client_announce_ready(Parent, Slogan, Extra) ->
+ ?SEV_IPRINT("announce ready (~p): ~p", [Slogan, Extra]),
+ ?SEV_ANNOUNCE_READY(Parent, Slogan, Extra).
+
+tpp_tcp_client_await_continue(Parent, Slogan) ->
+ ?SEV_IPRINT("await continue (~p)", [Slogan]),
+ case ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan) of
+ ok ->
+ ?SEV_IPRINT("continue (~p): ok", [Slogan]),
+ ok;
+ {ok, Data} ->
+ ?SEV_IPRINT("continue (~p): ok with data", [Slogan]),
+ Data;
+ {error, Reason} ->
+ ?SEV_EPRINT("continue (~p): error"
+ "~n ~p", [Slogan, Reason]),
+ exit({continue, Slogan, Reason})
+ end.
+
+tpp_tcp_client_await_terminate(Parent) ->
+ ?SEV_IPRINT("await terminate"),
+ case ?SEV_AWAIT_TERMINATE(Parent, parent) of
+ ok ->
+ ?SEV_IPRINT("termination received: normal"),
+ normal;
+ {error, Reason} ->
+ ?SEV_IPRINT("termination received: ~w", [Reason]),
+ Reason
+ end.
+
+tpp_tcp_client_msg_exchange(Sock, Send, Recv, InitMsg, Num) ->
+ Start = ?LIB:timestamp(),
+ tpp_tcp_client_msg_exchange_loop(Sock, Send, Recv, InitMsg,
+ Num, 0, 0, 0, Start).
+
+tpp_tcp_client_msg_exchange_loop(Sock, _Send, _Recv, _Msg,
+ Num, Num, Sent, Received,
+ Start) ->
+ Stop = ?LIB:timestamp(),
+ Info = socket:info(Sock),
+ case socket:close(Sock) of
+ ok ->
+ {Sent, Received, Info, Start, Stop};
+ {error, Reason} ->
+ exit({failed_closing, Reason})
+ end;
+tpp_tcp_client_msg_exchange_loop(Sock, Send, Recv, Data,
+ Num, N, Sent, Received, Start) ->
+ %% d("tpp_tcp_client_msg_exchange_loop(~w,~w) try send ~w", [Num,N,size(Data)]),
+ case tpp_tcp_send_req(Sock, Send, Data) of
+ {ok, SendSz} ->
+ %% d("tpp_tcp_client_msg_exchange_loop(~w,~w) sent - "
+ %% "now try recv", [Num,N]),
+ case tpp_tcp_recv_rep(Sock, Recv) of
+ {ok, NewData, RecvSz} ->
+ tpp_tcp_client_msg_exchange_loop(Sock, Send, Recv,
+ NewData, Num, N+1,
+ Sent+SendSz,
+ Received+RecvSz,
+ Start);
+ {error, RReason} ->
+ ?SEV_EPRINT("recv (~w of ~w): ~p: "
+ "~n ~p", [N, Num, RReason, mq()]),
+ exit({recv, RReason, N})
+ end;
+ {error, SReason} ->
+ ?SEV_EPRINT("send (~w of ~w): ~p"
+ "~n ~p", [N, Num, SReason, mq()]),
+ exit({send, SReason, N})
+ end.
+
+tpp_tcp_client_sock_open(Domain, Proto, BufInit) ->
+ case socket:open(Domain, stream, Proto) of
+ {ok, Sock} ->
+ ok = BufInit(Sock),
+ Sock;
+ {error, Reason} ->
+ exit({open_failed, Reason})
+ end.
+
+tpp_tcp_client_sock_bind(Sock, Domain) ->
+ LSA = which_local_socket_addr(Domain),
+ case socket:bind(Sock, LSA) of
+ {ok, _} ->
+ case socket:sockname(Sock) of
+ {ok, #{family := local, path := Path}} ->
+ Path;
+ {ok, _} ->
+ undefined;
+ {error, Reason1} ->
+ exit({sockname, Reason1})
+ end;
+ {error, Reason2} ->
+ exit({bind, Reason2})
+ end.
+
+tpp_tcp_client_sock_connect(Sock, ServerSA) ->
+ case socket:connect(Sock, ServerSA) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ exit({connect, Reason})
+ end.
+
+tpp_tcp_client_sock_close(Sock, Path) ->
+ case socket:close(Sock) of
+ ok ->
+ unlink_path(Path),
+ ok;
+ {error, closed} ->
+ ok;
+ {error, Reason} ->
+ ?SEV_EPRINT("failed closing: "
+ "~n Reason: ~p", [Reason]),
+ unlink_path(Path),
+ {error, {close, Reason}}
+ end.
+
+
+
+-define(TPP_REQUEST, 1).
+-define(TPP_REPLY, 2).
+
+tpp_tcp_recv_req(Sock, Recv) ->
+ tpp_tcp_recv(Sock, Recv, ?TPP_REQUEST).
+
+tpp_tcp_recv_rep(Sock, Recv) ->
+ tpp_tcp_recv(Sock, Recv, ?TPP_REPLY).
+
+tpp_tcp_recv(Sock, Recv, Tag) ->
+ case Recv(Sock, 0) of
+ {ok, <<Tag:32/integer, Sz:32/integer, Data/binary>> = Msg}
+ when (Sz =:= size(Data)) ->
+ %% We got it all
+ {ok, Data, size(Msg)};
+ {ok, <<Tag:32/integer, Sz:32/integer, Data/binary>> = Msg} ->
+ Remains = Sz - size(Data),
+ tpp_tcp_recv(Sock, Recv, Tag, Remains, size(Msg), [Data]);
+ {ok, <<Tag:32/integer, _/binary>>} ->
+ {error, {invalid_msg_tag, Tag}};
+ {error, _R} = ERROR ->
+ ERROR
+ end.
+
+tpp_tcp_recv(Sock, Recv, Tag, Remaining, AccSz, Acc) ->
+ case Recv(Sock, Remaining) of
+ {ok, Data} when (Remaining =:= size(Data)) ->
+ %% We got the rest
+ TotSz = AccSz + size(Data),
+ {ok, erlang:iolist_to_binary(lists:reverse([Data | Acc])), TotSz};
+ {ok, Data} when (Remaining > size(Data)) ->
+ tpp_tcp_recv(Sock, Recv, Tag,
+ Remaining - size(Data), AccSz + size(Data),
+ [Data | Acc]);
+ {error, _R} = ERROR ->
+ ERROR
+ end.
+
+
+tpp_tcp_send_req(Sock, Send, Data) ->
+ tpp_tcp_send(Sock, Send, ?TPP_REQUEST, Data).
+
+tpp_tcp_send_rep(Sock, Send, Data) ->
+ tpp_tcp_send(Sock, Send, ?TPP_REPLY, Data).
+
+tpp_tcp_send(Sock, Send, Tag, Data) ->
+ DataSz = size(Data),
+ Msg = <<Tag:32/integer, DataSz:32/integer, Data/binary>>,
+ tpp_tcp_send_msg(Sock, Send, Msg, 0).
+
+tpp_tcp_send_msg(Sock, Send, Msg, AccSz) when is_binary(Msg) ->
+ case Send(Sock, Msg) of
+ ok ->
+ {ok, AccSz+size(Msg)};
+ {ok, Rest} -> % This is an IOVec
+ RestBin = list_to_binary(Rest),
+ tpp_tcp_send_msg(Sock, Send, RestBin, AccSz+(size(Msg)-size(RestBin)));
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+%% size_of_data(Data) when is_binary(Data) ->
+%% size(Data);
+%% size_of_data(Data) when is_list(Data) ->
+%% size_of_iovec(Data, 0).
+
+%% size_of_iovec([], Sz) ->
+%% Sz;
+%% size_of_iovec([B|IOVec], Sz) ->
+%% size_of_iovec(IOVec, Sz+size(B)).
+
+mq() ->
+ mq(self()).
+
+mq(Pid) when is_pid(Pid) ->
+ Tag = messages,
+ {Tag, Msgs} = process_info(Pid, Tag),
+ Msgs.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Ping-Pong for UDP
+
+traffic_ping_pong_sendto_and_recvfrom_udp(InitState) ->
+ Send = fun(Sock, Data, Dest) ->
+ socket:sendto(Sock, Data, Dest)
+ end,
+ Recv = fun(Sock, Sz) ->
+ socket:recvfrom(Sock, Sz)
+ end,
+ InitState2 = InitState#{send => Send, % Send function
+ recv => Recv % Receive function
+ },
+ traffic_ping_pong_send_and_receive_udp(InitState2).
+
+traffic_ping_pong_sendmsg_and_recvmsg_udp(InitState) ->
+ Send = fun(Sock, Data, Dest) when is_binary(Data) ->
+ MsgHdr = #{addr => Dest, iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr);
+ (Sock, Data, Dest) when is_list(Data) -> %% We assume iovec...
+ MsgHdr = #{addr => Dest, iov => Data},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ Recv = fun(Sock, Sz) ->
+ case socket:recvmsg(Sock, Sz, 0) of
+ {ok, #{addr := Source,
+ iov := [Data]}} ->
+ {ok, {Source, Data}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState2 = InitState#{send => Send, % Send function
+ recv => Recv % Receive function
+ },
+ traffic_ping_pong_send_and_receive_udp(InitState2).
+
+
+traffic_ping_pong_send_and_receive_udp(#{msg := Msg} = InitState) ->
+ Fun = fun(Sock) ->
+ {ok, RcvSz} = socket:getopt(Sock, socket, rcvbuf),
+ if (RcvSz =< (8+size(Msg))) ->
+ i("adjust socket rcvbuf buffer size"),
+ ok = socket:setopt(Sock, socket, rcvbuf, 1024+size(Msg));
+ true ->
+ ok
+ end,
+ {ok, SndSz} = socket:getopt(Sock, socket, sndbuf),
+ if (SndSz =< (8+size(Msg))) ->
+ i("adjust socket sndbuf buffer size"),
+ ok = socket:setopt(Sock, socket, sndbuf, 1024+size(Msg));
+ true ->
+ ok
+ end,
+ {ok, OtpRcvBuf} = socket:getopt(Sock, otp, rcvbuf),
+ if
+ (OtpRcvBuf =< (8+size(Msg))) ->
+ i("adjust otp rcvbuf buffer size"),
+ ok = socket:setopt(Sock, otp, rcvbuf, 1024+size(Msg));
+ true ->
+ ok
+ end
+ end,
+ traffic_ping_pong_send_and_receive_udp2(InitState#{buf_init => Fun}).
+
+traffic_ping_pong_send_and_receive_udp2(InitState) ->
+ ServerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain, proto := Proto} = State) ->
+ case socket:open(Domain, dgram, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{domain := local,
+ sock := Sock, local_sa := LSA} = _State) ->
+ case socket:bind(Sock, LSA) of
+ {ok, _} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end;
+ (#{sock := Sock, local_sa := LSA} = State) ->
+ case sock_bind(Sock, LSA) of
+ {ok, Port} ->
+ {ok, State#{port => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "maybe init buffers",
+ cmd => fun(#{sock := Sock, buf_init := BufInit} = _State) ->
+ BufInit(Sock)
+ end},
+ #{desc => "create handler",
+ cmd => fun(State) ->
+ Handler = tpp_udp_server_handler_create(),
+ ?SEV_IPRINT("handler created: ~p", [Handler]),
+ {ok, State#{handler => Handler}}
+ end},
+ #{desc => "monitor handler",
+ cmd => fun(#{handler := Handler} = _State) ->
+ _MRef = erlang:monitor(process, Handler),
+ ok
+ end},
+ #{desc => "start handler",
+ cmd => fun(#{handler := Handler,
+ sock := Sock,
+ send := Send,
+ recv := Recv} = _State) ->
+ ?SEV_ANNOUNCE_START(Handler, {Sock, Send, Recv}),
+ ok
+ end},
+ #{desc => "await handler ready (init)",
+ cmd => fun(#{tester := Tester,
+ handler := Handler} = State) ->
+ case ?SEV_AWAIT_READY(Handler, handler, init,
+ [{tester, Tester}]) of
+ ok ->
+ {ok, maps:remove(csock, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{domain := local,
+ tester := Tester, local_sa := LSA}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, LSA),
+ ok;
+ (#{tester := Tester, local_sa := LSA, port := Port}) ->
+ ServerSA = LSA#{port => Port},
+ ?SEV_ANNOUNCE_READY(Tester, init, ServerSA),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (recv)",
+ cmd => fun(#{tester := Tester,
+ handler := Handler} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv,
+ [{handler, Handler}])
+ end},
+ #{desc => "order handler to recv",
+ cmd => fun(#{handler := Handler,
+ sock := _Sock} = _State) ->
+ %% socket:setopt(Sock, otp, debug, true),
+ ?SEV_ANNOUNCE_CONTINUE(Handler, recv),
+ ok
+ end},
+ #{desc => "await continue (close)",
+ cmd => fun(#{tester := Tester,
+ handler := Handler} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, close,
+ [{handler, Handler}])
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ %% socket:setopt(Sock, otp, debug, true),
+ case socket:close(Sock) of
+ ok ->
+ {ok, maps:remove(sock, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "(maybe) unlink socket",
+ cmd => fun(#{domain := local,
+ local_sa := #{path := Path}} = State) ->
+ unlink_path(Path,
+ fun() ->
+ {ok, maps:remove(local_sa, State)}
+ end,
+ fun() ->
+ ok
+ end);
+ (_) ->
+ ok
+ end},
+ #{desc => "announce ready (close)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_ANNOUNCE_READY(Tester, close),
+ ok
+ end},
+ #{desc => "await handler ready (recv)",
+ cmd => fun(#{tester := Tester,
+ handler := Handler} = State) ->
+ case ?SEV_AWAIT_READY(Handler, handler, recv,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ %% ?SEV_IPRINT("Result: ~p", [Result]),
+ {ok, State#{result => Result}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv)",
+ cmd => fun(#{tester := Tester,
+ result := Result} = State) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv, Result),
+ {ok, maps:remove(result, State)}
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "stop handler",
+ cmd => fun(#{handler := Handler}) ->
+ ?SEV_ANNOUNCE_TERMINATE(Handler),
+ ok
+ end},
+ #{desc => "await handler termination",
+ cmd => fun(#{handler := Handler} = State) ->
+ ?SEV_AWAIT_TERMINATION(Handler),
+ State1 = maps:remove(handler, State),
+ {ok, State1}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ ClientSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ {Tester, ServerSA} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ server_sa => ServerSA}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "create node",
+ cmd => fun(#{host := Host} = State) ->
+ case start_node(Host, client) of
+ {ok, Node} ->
+ ?SEV_IPRINT("(remote) client node ~p started",
+ [Node]),
+ {ok, State#{node => Node}};
+ {error, Reason} ->
+ {skip, Reason}
+ end
+ end},
+ #{desc => "monitor client node",
+ cmd => fun(#{node := Node} = _State) ->
+ true = erlang:monitor_node(Node, true),
+ ok
+ end},
+ #{desc => "create (remote) handler",
+ cmd => fun(#{node := Node} = State) ->
+ Pid = tpp_udp_client_handler_create(Node),
+ ?SEV_IPRINT("handler created: ~p", [Pid]),
+ {ok, State#{handler => Pid}}
+ end},
+ #{desc => "monitor remote handler",
+ cmd => fun(#{handler := Pid}) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order remote handler to start",
+ cmd => fun(#{handler := Handler,
+ server_sa := ServerSA,
+ proto := Proto,
+ buf_init := BufInit,
+ send := Send,
+ recv := Recv}) ->
+ ?SEV_ANNOUNCE_START(Handler,
+ {ServerSA, Proto, BufInit,
+ Send, Recv}),
+ ok
+ end},
+ #{desc => "await (remote) handler ready",
+ cmd => fun(#{tester := Tester,
+ handler := Handler} = _State) ->
+ ?SEV_AWAIT_READY(Handler, handler, init,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (send)",
+ cmd => fun(#{tester := Tester,
+ handler := Handler} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester,
+ send,
+ [{handler, Handler}])
+ end},
+ #{desc => "order handler to continue (send)",
+ cmd => fun(#{handler := Handler,
+ msg := Msg,
+ num := Num} = State) ->
+ Data = {Msg, Num},
+ ?SEV_ANNOUNCE_CONTINUE(Handler, send, Data),
+ {ok, maps:remove(data, State)}
+ end},
+ #{desc => "await remote handler ready (send)",
+ cmd => fun(#{tester := Tester,
+ handler := Handler} = State) ->
+ case ?SEV_AWAIT_READY(Handler, handler, send,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ %% ?SEV_IPRINT("remote client result: "
+ %% "~n ~p", [Result]),
+ {ok, State#{result => Result}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (send)",
+ cmd => fun(#{tester := Tester, result := Result} = State) ->
+ ?SEV_ANNOUNCE_READY(Tester, send, Result),
+ {ok, maps:remove(result, State)}
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester,
+ handler := Handler} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester,
+ [{handler, Handler}]) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "stop (remote) handler",
+ cmd => fun(#{handler := Handler}) ->
+ ?SEV_ANNOUNCE_TERMINATE(Handler),
+ ok
+ end},
+ #{desc => "await (remote) handler termination",
+ cmd => fun(#{handler := Handler} = State) ->
+ ?SEV_AWAIT_TERMINATION(Handler),
+ State1 = maps:remove(handler, State),
+ {ok, State1}
+ end},
+ #{desc => "stop client node",
+ cmd => fun(#{node := Node} = _State) ->
+ stop_node(Node)
+ end},
+ #{desc => "await client node termination",
+ cmd => fun(#{node := Node} = State) ->
+ receive
+ {nodedown, Node} ->
+ {ok, maps:remove(node, State)}
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = State) ->
+ {ok, ServerSA} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{server_sa => ServerSA}}
+ end},
+
+ %% Start the client
+ #{desc => "order client start",
+ cmd => fun(#{client := Pid,
+ server_sa := ServerSA} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, ServerSA),
+ ok
+ end},
+ #{desc => "await client ready (init)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client, init)
+ end},
+
+ %% The actual test
+ #{desc => "order server continue (recv)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order client continue (send)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, send),
+ ok
+ end},
+ #{desc => "await client ready (send)",
+ cmd => fun(#{server := Server,
+ client := Client} = State) ->
+ case ?SEV_AWAIT_READY(Client, client, send,
+ [{server, Server}]) of
+ {ok, {_, _, _, _} = Result} ->
+ ?SEV_IPRINT("client result: "
+ "~n ~p", [Result]),
+ {ok, State#{client_result => Result}};
+ {ok, BadResult} ->
+ ?SEV_EPRINT("client result: "
+ "~n ~p", [BadResult]),
+ {error, {invalid_client_result, BadResult}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order server continue (close)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, close),
+ ok
+ end},
+ #{desc => "await server ready (close)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, server, close)
+ end},
+ %% Because of the way we control the server, there is no real
+ %% point in collecting statistics from it (the time will include
+ %% our communication with it).
+ #{desc => "await server ready (recv)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Server, server, recv,
+ [{client, Client}]) of
+ {ok, _Result} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "present result",
+ cmd => fun(#{client_result := CRes,
+ num := Num} = State) ->
+ {CSent, CReceived, CStart, CStop} = CRes,
+ CTime = tdiff(CStart, CStop),
+ %% Note that the sizes we are counting is only
+ %% the "data" part of the messages. There is also
+ %% fixed header for each message, which of cource
+ %% is small for the large messages, but comparatively
+ %% big for the small messages!
+ ?SEV_IPRINT("Results: ~w messages exchanged"
+ "~n Client: ~w msec"
+ "~n ~.2f msec/message (roundtrip)"
+ "~n ~.2f messages/msec (roundtrip)"
+ "~n ~w bytes/msec sent"
+ "~n ~w bytes/msec received",
+ [Num,
+ CTime,
+ CTime / Num,
+ Num / CTime,
+ CSent div CTime,
+ CReceived div CTime]),
+ State1 = maps:remove(client_result, State),
+ {ok, State1}
+ end},
+
+ %% Terminations
+ #{desc => "order client to terminate",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await client termination",
+ cmd => fun(#{client := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(client, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(server, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ i("start server evaluator"),
+ ServerInitState = #{domain => maps:get(domain, InitState),
+ proto => maps:get(proto, InitState),
+ recv => maps:get(recv, InitState),
+ send => maps:get(send, InitState),
+ buf_init => maps:get(buf_init, InitState)},
+ Server = ?SEV_START("server", ServerSeq, ServerInitState),
+
+ i("start client evaluator(s)"),
+ ClientInitState = InitState#{host => local_host()},
+ Client = ?SEV_START("client", ClientSeq, ClientInitState),
+
+ i("start 'tester' evaluator"),
+ TesterInitState = #{server => Server#ev.pid,
+ client => Client#ev.pid,
+ num => maps:get(num, InitState)},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]).
+
+
+
+%% Server side handler process
+%% We don't actually need a separate process for this socket,
+%% but we do it anyway to simplify the sequence.
+tpp_udp_server_handler_create() ->
+ Self = self(),
+ erlang:spawn(fun() -> tpp_udp_server_handler(Self) end).
+
+tpp_udp_server_handler(Parent) ->
+ tpp_udp_server_handler_init(Parent),
+ {Sock, Send, Recv} = tpp_udp_handler_await_start(Parent),
+ tpp_udp_handler_announce_ready(Parent, init),
+ tpp_udp_handler_await_continue(Parent, recv),
+ Result = tpp_udp_server_handler_msg_exchange(Sock, Send, Recv),
+ tpp_udp_handler_announce_ready(Parent, recv, Result),
+ Reason = tpp_udp_handler_await_terminate(Parent),
+ ?SEV_IPRINT("terminating"),
+ exit(Reason).
+
+tpp_udp_server_handler_init(Parent) ->
+ put(sname, "shandler"),
+ ?SEV_IPRINT("init"),
+ _MRef = erlang:monitor(process, Parent),
+ ok.
+
+tpp_udp_server_handler_msg_exchange(Sock, Send, Recv) ->
+ tpp_udp_server_handler_msg_exchange_loop(Sock, Send, Recv,
+ 0, 0, 0, undefined).
+
+tpp_udp_server_handler_msg_exchange_loop(Sock, Send, Recv,
+ N, Sent, Received, Start) ->
+ %% ?SEV_IPRINT("[~w] try receive", [N]),
+ %% if
+ %% (N =:= (?TPP_SMALL_NUM-2)) ->
+ %% ?SEV_IPRINT("[~w] try receive", [N]),
+ %% socket:setopt(Sock, otp, debug, true);
+ %% true -> ok
+ %% end,
+ try tpp_udp_recv_req(Sock, Recv) of
+ {ok, Msg, RecvSz, From} ->
+ NewStart = if (Start =:= undefined) -> ?LIB:timestamp();
+ true -> Start end,
+ %% ?SEV_IPRINT("[~w] received - now try send", [N]),
+ try tpp_udp_send_rep(Sock, Send, Msg, From) of
+ {ok, SendSz} ->
+ tpp_udp_server_handler_msg_exchange_loop(Sock, Send, Recv,
+ N+1,
+ Sent+SendSz,
+ Received+RecvSz,
+ NewStart);
+ {error, SReason} ->
+ ?SEV_EPRINT("send (~w): ~p", [N, SReason]),
+ exit({send, SReason, N})
+ catch
+ SC:SE:SS ->
+ exit({send, {SC, SE, SS}, N})
+ end;
+ {error, closed} ->
+ ?SEV_IPRINT("closed - we are done: ~w, ~w, ~w",
+ [N, Sent, Received]),
+ Stop = ?LIB:timestamp(),
+ {N, Sent, Received, Start, Stop};
+ {error, RReason} ->
+ ?SEV_EPRINT("recv (~w): ~p", [N, RReason]),
+ exit({recv, RReason, N})
+ catch
+ RC:RE:RS ->
+ exit({recv, {RC, RE, RS}, N})
+ end.
+
+
+%% The (remote) client side handler process
+
+tpp_udp_client_handler_create(Node) ->
+ Self = self(),
+ Fun = fun() -> put(sname, "chandler"), tpp_udp_client_handler(Self) end,
+ erlang:spawn(Node, Fun).
+
+tpp_udp_client_handler(Parent) ->
+ tpp_udp_client_handler_init(Parent),
+ ?SEV_IPRINT("await start command"),
+ {ServerSA, Proto, BufInit, Send, Recv} = tpp_udp_handler_await_start(Parent),
+ ?SEV_IPRINT("start command with"
+ "~n ServerSA: ~p", [ServerSA]),
+ Domain = maps:get(family, ServerSA),
+ Sock = tpp_udp_sock_open(Domain, Proto, BufInit),
+ Path = tpp_udp_sock_bind(Sock, Domain),
+ ?SEV_IPRINT("announce ready", []),
+ tpp_udp_handler_announce_ready(Parent, init),
+ {InitMsg, Num} = tpp_udp_handler_await_continue(Parent, send),
+ ?SEV_IPRINT("received continue with"
+ "~n Num: ~p", [Num]),
+ Result = tpp_udp_client_handler_msg_exchange(Sock, ServerSA,
+ Send, Recv, InitMsg, Num),
+ ?SEV_IPRINT("ready"),
+ tpp_udp_handler_announce_ready(Parent, send, Result),
+ ?SEV_IPRINT("await terminate"),
+ Reason = tpp_udp_handler_await_terminate(Parent),
+ ?SEV_IPRINT("terminate with ~p", [Reason]),
+ tpp_udp_sock_close(Sock, Path),
+ ?SEV_IPRINT("terminating"),
+ exit(Reason).
+
+tpp_udp_client_handler_init(Parent) ->
+ put(sname, "chandler"),
+ ?SEV_IPRINT("init"),
+ _MRef = erlang:monitor(process, Parent),
+ ok.
+
+tpp_udp_client_handler_msg_exchange(Sock, ServerSA,
+ Send, Recv, InitMsg, Num) ->
+ Start = ?LIB:timestamp(),
+ tpp_udp_client_handler_msg_exchange_loop(Sock, ServerSA,
+ Send, Recv, InitMsg,
+ Num, 0, 0, 0, Start).
+
+tpp_udp_client_handler_msg_exchange_loop(_Sock, _Dest, _Send, _Recv, _Msg,
+ Num, Num, Sent, Received,
+ Start) ->
+ Stop = ?LIB:timestamp(),
+ {Sent, Received, Start, Stop};
+tpp_udp_client_handler_msg_exchange_loop(Sock,
+ #{family := local} = Dest,
+ Send, Recv, Data,
+ Num, N, Sent, Received, Start) ->
+ case tpp_udp_send_req(Sock, Send, Data, Dest) of
+ {ok, SendSz} ->
+ case tpp_udp_recv_rep(Sock, Recv) of
+ {ok, NewData, RecvSz, Dest} ->
+ tpp_udp_client_handler_msg_exchange_loop(Sock, Dest,
+ Send, Recv,
+ NewData, Num, N+1,
+ Sent+SendSz,
+ Received+RecvSz,
+ Start);
+ {error, RReason} ->
+ ?SEV_EPRINT("recv (~w of ~w): ~p", [N, Num, RReason]),
+ exit({recv, RReason, N})
+ end;
+ {error, SReason} ->
+ ?SEV_EPRINT("send (~w of ~w): ~p", [N, Num, SReason]),
+ exit({send, SReason, N})
+ end;
+tpp_udp_client_handler_msg_exchange_loop(Sock,
+ #{addr := Addr, port := Port} = Dest0,
+ Send, Recv, Data,
+ Num, N, Sent, Received, Start) ->
+ case tpp_udp_send_req(Sock, Send, Data, Dest0) of
+ {ok, SendSz} ->
+ case tpp_udp_recv_rep(Sock, Recv) of
+ {ok, NewData, RecvSz, #{addr := Addr, port := Port} = Dest1} ->
+ tpp_udp_client_handler_msg_exchange_loop(Sock, Dest1,
+ Send, Recv,
+ NewData, Num, N+1,
+ Sent+SendSz,
+ Received+RecvSz,
+ Start);
+ {error, RReason} ->
+ ?SEV_EPRINT("recv (~w of ~w): ~p", [N, Num, RReason]),
+ exit({recv, RReason, N})
+ end;
+ {error, SReason} ->
+ ?SEV_EPRINT("send (~w of ~w): ~p", [N, Num, SReason]),
+ exit({send, SReason, N})
+ end.
+
+
+tpp_udp_recv_req(Sock, Recv) ->
+ tpp_udp_recv(Sock, Recv, ?TPP_REQUEST).
+
+tpp_udp_recv_rep(Sock, Recv) ->
+ tpp_udp_recv(Sock, Recv, ?TPP_REPLY).
+
+tpp_udp_recv(Sock, Recv, Tag) ->
+ %% ok = socket:setopt(Sock, otp, debug, true),
+ try Recv(Sock, 0) of
+ {ok, {Source, <<Tag:32/integer, Sz:32/integer, Data/binary>> = Msg}}
+ when (Sz =:= size(Data)) ->
+ %% ok = socket:setopt(Sock, otp, debug, false),
+ %% We got it all
+ %% ?SEV_IPRINT("tpp_udp_recv -> got all: "
+ %% "~n Source: ~p"
+ %% "~n Tag: ~p"
+ %% "~n Sz: ~p"
+ %% "~n size(Data): ~p",
+ %% [Source, Tag, Sz, size(Data)]),
+ {ok, Data, size(Msg), Source};
+ {ok, {_Source, <<Tag:32/integer, Sz:32/integer, Data/binary>>}} ->
+ %% ok = socket:setopt(Sock, otp, debug, false),
+ {error, {invalid_msg, Sz, size(Data)}};
+ {ok, {_, <<Tag:32/integer, _/binary>>}} ->
+ %% ok = socket:setopt(Sock, otp, debug, false),
+ {error, {invalid_msg_tag, Tag}};
+ {error, _} = ERROR ->
+ %% ok = socket:setopt(Sock, otp, debug, false),
+ ERROR
+ catch
+ C:E:S ->
+ {error, {catched, C, E, S}}
+ end.
+
+tpp_udp_send_req(Sock, Send, Data, Dest) ->
+ tpp_udp_send(Sock, Send, ?TPP_REQUEST, Data, Dest).
+
+tpp_udp_send_rep(Sock, Send, Data, Dest) ->
+ tpp_udp_send(Sock, Send, ?TPP_REPLY, Data, Dest).
+
+tpp_udp_send(Sock, Send, Tag, Data, Dest) ->
+ DataSz = size(Data),
+ Msg = <<Tag:32/integer, DataSz:32/integer, Data/binary>>,
+ tpp_udp_send_msg(Sock, Send, Msg, Dest, 0).
+
+tpp_udp_send_msg(Sock, Send, Msg, Dest, AccSz) when is_binary(Msg) ->
+ case Send(Sock, Msg, Dest) of
+ ok ->
+ {ok, AccSz+size(Msg)};
+ {ok, Rest} -> % This is an IOVec
+ RestBin = list_to_binary(Rest),
+ tpp_udp_send_msg(Sock, Send, RestBin, Dest,
+ AccSz+(size(Msg)-size(RestBin)));
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+tpp_udp_handler_await_start(Parent) ->
+ ?SEV_IPRINT("await start"),
+ ?SEV_AWAIT_START(Parent).
+
+tpp_udp_handler_announce_ready(Parent, Slogan) ->
+ ?SEV_IPRINT("announce ready (~p)", [Slogan]),
+ ?SEV_ANNOUNCE_READY(Parent, Slogan).
+tpp_udp_handler_announce_ready(Parent, Slogan, Extra) ->
+ ?SEV_IPRINT("announce ready (~p)", [Slogan]),
+ ?SEV_ANNOUNCE_READY(Parent, Slogan, Extra).
+
+tpp_udp_handler_await_continue(Parent, Slogan) ->
+ ?SEV_IPRINT("await continue (~p)", [Slogan]),
+ case ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan) of
+ ok ->
+ ?SEV_IPRINT("continue (~p): ok", [Slogan]),
+ ok;
+ {ok, Data} ->
+ ?SEV_IPRINT("continue (~p): ok with data", [Slogan]),
+ Data;
+ {error, Reason} ->
+ ?SEV_EPRINT("continue (~p): error"
+ "~n ~p", [Slogan, Reason]),
+ exit({continue, Slogan, Reason})
+ end.
+
+tpp_udp_handler_await_terminate(Parent) ->
+ ?SEV_IPRINT("await terminate"),
+ case ?SEV_AWAIT_TERMINATE(Parent, parent) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ Reason
+ end.
+
+
+tpp_udp_sock_open(Domain, Proto, BufInit) ->
+ case socket:open(Domain, dgram, Proto) of
+ {ok, Sock} ->
+ ok = BufInit(Sock),
+ Sock;
+ {error, Reason} ->
+ exit({open_failed, Reason})
+ end.
+
+tpp_udp_sock_bind(Sock, Domain) ->
+ LSA = which_local_socket_addr(Domain),
+ case socket:bind(Sock, LSA) of
+ {ok, _} ->
+ ok;
+ {error, Reason} ->
+ exit({bind, Reason})
+ end.
+
+tpp_udp_sock_close(Sock, Path) ->
+ case socket:close(Sock) of
+ ok ->
+ unlink_path(Path),
+ ok;
+ {error, Reason} ->
+ ?SEV_EPRINT("Failed closing socket: "
+ "~n ~p", [Reason]),
+ unlink_path(Path),
+ {error, {close, Reason}}
+ end.
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% %%
+%% TIME TEST %%
+%% %%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgenf_cgenf_small_tcp4(suite) ->
+ [];
+ttest_sgenf_cgenf_small_tcp4(doc) ->
+ [];
+ttest_sgenf_cgenf_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgenf_small_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ gen, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgenf_cgenf_small_tcp6(suite) ->
+ [];
+ttest_sgenf_cgenf_small_tcp6(doc) ->
+ [];
+ttest_sgenf_cgenf_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgenf_small_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ gen, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgenf_cgenf_medium_tcp4(suite) ->
+ [];
+ttest_sgenf_cgenf_medium_tcp4(doc) ->
+ [];
+ttest_sgenf_cgenf_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgenf_medium_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ gen, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgenf_cgenf_medium_tcp6(suite) ->
+ [];
+ttest_sgenf_cgenf_medium_tcp6(doc) ->
+ [];
+ttest_sgenf_cgenf_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgenf_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ gen, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgenf_cgenf_large_tcp4(suite) ->
+ [];
+ttest_sgenf_cgenf_large_tcp4(doc) ->
+ [];
+ttest_sgenf_cgenf_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgenf_large_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ gen, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgenf_cgenf_large_tcp6(suite) ->
+ [];
+ttest_sgenf_cgenf_large_tcp6(doc) ->
+ [];
+ttest_sgenf_cgenf_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgenf_large_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ gen, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgenf_cgeno_small_tcp4(suite) ->
+ [];
+ttest_sgenf_cgeno_small_tcp4(doc) ->
+ [];
+ttest_sgenf_cgeno_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgeno_small_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ gen, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgenf_cgeno_small_tcp6(suite) ->
+ [];
+ttest_sgenf_cgeno_small_tcp6(doc) ->
+ [];
+ttest_sgenf_cgeno_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgeno_small_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ gen, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgenf_cgeno_medium_tcp4(suite) ->
+ [];
+ttest_sgenf_cgeno_medium_tcp4(doc) ->
+ [];
+ttest_sgenf_cgeno_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgeno_medium_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ gen, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgenf_cgeno_medium_tcp6(suite) ->
+ [];
+ttest_sgenf_cgeno_medium_tcp6(doc) ->
+ [];
+ttest_sgenf_cgeno_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgeno_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ gen, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgenf_cgeno_large_tcp4(suite) ->
+ [];
+ttest_sgenf_cgeno_large_tcp4(doc) ->
+ [];
+ttest_sgenf_cgeno_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgeno_large_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ gen, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgenf_cgeno_large_tcp6(suite) ->
+ [];
+ttest_sgenf_cgeno_large_tcp6(doc) ->
+ [];
+ttest_sgenf_cgeno_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgeno_large_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ gen, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgenf_cgent_small_tcp4(suite) ->
+ [];
+ttest_sgenf_cgent_small_tcp4(doc) ->
+ [];
+ttest_sgenf_cgent_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgent_small_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ gen, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgenf_cgent_small_tcp6(suite) ->
+ [];
+ttest_sgenf_cgent_small_tcp6(doc) ->
+ [];
+ttest_sgenf_cgent_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgeno_small_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ gen, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgenf_cgent_medium_tcp4(suite) ->
+ [];
+ttest_sgenf_cgent_medium_tcp4(doc) ->
+ [];
+ttest_sgenf_cgent_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgent_medium_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ gen, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgenf_cgent_medium_tcp6(suite) ->
+ [];
+ttest_sgenf_cgent_medium_tcp6(doc) ->
+ [];
+ttest_sgenf_cgent_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgent_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ gen, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgenf_cgent_large_tcp4(suite) ->
+ [];
+ttest_sgenf_cgent_large_tcp4(doc) ->
+ [];
+ttest_sgenf_cgent_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgent_large_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ gen, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgenf_cgent_large_tcp6(suite) ->
+ [];
+ttest_sgenf_cgent_large_tcp6(doc) ->
+ [];
+ttest_sgenf_cgent_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgent_large_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ gen, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgenf_csockf_small_tcp4(suite) ->
+ [];
+ttest_sgenf_csockf_small_tcp4(doc) ->
+ [];
+ttest_sgenf_csockf_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csockf_small_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ sock, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgenf_csockf_small_tcp6(suite) ->
+ [];
+ttest_sgenf_csockf_small_tcp6(doc) ->
+ [];
+ttest_sgenf_csockf_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csockf_small_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ sock, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgenf_csockf_medium_tcp4(suite) ->
+ [];
+ttest_sgenf_csockf_medium_tcp4(doc) ->
+ [];
+ttest_sgenf_csockf_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csockf_medium_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ sock, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgenf_csockf_medium_tcp6(suite) ->
+ [];
+ttest_sgenf_csockf_medium_tcp6(doc) ->
+ [];
+ttest_sgenf_csockf_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csockf_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ sock, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgenf_csockf_large_tcp4(suite) ->
+ [];
+ttest_sgenf_csockf_large_tcp4(doc) ->
+ [];
+ttest_sgenf_csockf_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csockf_large_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ sock, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgenf_csockf_large_tcp6(suite) ->
+ [];
+ttest_sgenf_csockf_large_tcp6(doc) ->
+ [];
+ttest_sgenf_csockf_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csockf_large_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ sock, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgenf_csocko_small_tcp4(suite) ->
+ [];
+ttest_sgenf_csocko_small_tcp4(doc) ->
+ [];
+ttest_sgenf_csocko_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csocko_small_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ sock, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgenf_csocko_small_tcp6(suite) ->
+ [];
+ttest_sgenf_csocko_small_tcp6(doc) ->
+ [];
+ttest_sgenf_csocko_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csocko_small_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ sock, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgenf_csocko_medium_tcp4(suite) ->
+ [];
+ttest_sgenf_csocko_medium_tcp4(doc) ->
+ [];
+ttest_sgenf_csocko_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csocko_medium_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ sock, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgenf_csocko_medium_tcp6(suite) ->
+ [];
+ttest_sgenf_csocko_medium_tcp6(doc) ->
+ [];
+ttest_sgenf_csocko_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csocko_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ sock, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgenf_csocko_large_tcp4(suite) ->
+ [];
+ttest_sgenf_csocko_large_tcp4(doc) ->
+ [];
+ttest_sgenf_csocko_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csocko_large_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ sock, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgenf_csocko_large_tcp6(suite) ->
+ [];
+ttest_sgenf_csocko_large_tcp6(doc) ->
+ [];
+ttest_sgenf_csocko_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csocko_large_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ sock, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgenf_csockt_small_tcp4(suite) ->
+ [];
+ttest_sgenf_csockt_small_tcp4(doc) ->
+ [];
+ttest_sgenf_csockt_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csockt_small_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ sock, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgenf_csockt_small_tcp6(suite) ->
+ [];
+ttest_sgenf_csockt_small_tcp6(doc) ->
+ [];
+ttest_sgenf_csockt_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csocko_small_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ sock, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgenf_csockt_medium_tcp4(suite) ->
+ [];
+ttest_sgenf_csockt_medium_tcp4(doc) ->
+ [];
+ttest_sgenf_csockt_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csockt_medium_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ sock, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgenf_csockt_medium_tcp6(suite) ->
+ [];
+ttest_sgenf_csockt_medium_tcp6(doc) ->
+ [];
+ttest_sgenf_csockt_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csockt_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ sock, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgenf_csockt_large_tcp4(suite) ->
+ [];
+ttest_sgenf_csockt_large_tcp4(doc) ->
+ [];
+ttest_sgenf_csockt_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csockt_large_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ sock, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgenf_csockt_large_tcp6(suite) ->
+ [];
+ttest_sgenf_csockt_large_tcp6(doc) ->
+ [];
+ttest_sgenf_csockt_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csockt_large_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ sock, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgeno_cgenf_small_tcp4(suite) ->
+ [];
+ttest_sgeno_cgenf_small_tcp4(doc) ->
+ [];
+ttest_sgeno_cgenf_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgenf_small_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ gen, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgeno_cgenf_small_tcp6(suite) ->
+ [];
+ttest_sgeno_cgenf_small_tcp6(doc) ->
+ [];
+ttest_sgeno_cgenf_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgenf_small_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ gen, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgeno_cgenf_medium_tcp4(suite) ->
+ [];
+ttest_sgeno_cgenf_medium_tcp4(doc) ->
+ [];
+ttest_sgeno_cgenf_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgenf_medium_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ gen, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgeno_cgenf_medium_tcp6(suite) ->
+ [];
+ttest_sgeno_cgenf_medium_tcp6(doc) ->
+ [];
+ttest_sgeno_cgenf_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgenf_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ gen, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgeno_cgenf_large_tcp4(suite) ->
+ [];
+ttest_sgeno_cgenf_large_tcp4(doc) ->
+ [];
+ttest_sgeno_cgenf_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgenf_large_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ gen, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgeno_cgenf_large_tcp6(suite) ->
+ [];
+ttest_sgeno_cgenf_large_tcp6(doc) ->
+ [];
+ttest_sgeno_cgenf_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgenf_large_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ gen, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgeno_cgeno_small_tcp4(suite) ->
+ [];
+ttest_sgeno_cgeno_small_tcp4(doc) ->
+ [];
+ttest_sgeno_cgeno_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgeno_small_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ gen, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgeno_cgeno_small_tcp6(suite) ->
+ [];
+ttest_sgeno_cgeno_small_tcp6(doc) ->
+ [];
+ttest_sgeno_cgeno_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgeno_small_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ gen, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgeno_cgeno_medium_tcp4(suite) ->
+ [];
+ttest_sgeno_cgeno_medium_tcp4(doc) ->
+ [];
+ttest_sgeno_cgeno_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgeno_medium_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ gen, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgeno_cgeno_medium_tcp6(suite) ->
+ [];
+ttest_sgeno_cgeno_medium_tcp6(doc) ->
+ [];
+ttest_sgeno_cgeno_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgeno_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ gen, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgeno_cgeno_large_tcp4(suite) ->
+ [];
+ttest_sgeno_cgeno_large_tcp4(doc) ->
+ [];
+ttest_sgeno_cgeno_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgeno_large_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ gen, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgeno_cgeno_large_tcp6(suite) ->
+ [];
+ttest_sgeno_cgeno_large_tcp6(doc) ->
+ [];
+ttest_sgeno_cgeno_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgeno_large_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ gen, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgeno_cgent_small_tcp4(suite) ->
+ [];
+ttest_sgeno_cgent_small_tcp4(doc) ->
+ [];
+ttest_sgeno_cgent_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgent_small_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ gen, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgeno_cgent_small_tcp6(suite) ->
+ [];
+ttest_sgeno_cgent_small_tcp6(doc) ->
+ [];
+ttest_sgeno_cgent_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgeno_small_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ gen, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgeno_cgent_medium_tcp4(suite) ->
+ [];
+ttest_sgeno_cgent_medium_tcp4(doc) ->
+ [];
+ttest_sgeno_cgent_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgent_medium_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ gen, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgeno_cgent_medium_tcp6(suite) ->
+ [];
+ttest_sgeno_cgent_medium_tcp6(doc) ->
+ [];
+ttest_sgeno_cgent_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgent_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ gen, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgeno_cgent_large_tcp4(suite) ->
+ [];
+ttest_sgeno_cgent_large_tcp4(doc) ->
+ [];
+ttest_sgeno_cgent_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgent_large_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ gen, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgeno_cgent_large_tcp6(suite) ->
+ [];
+ttest_sgeno_cgent_large_tcp6(doc) ->
+ [];
+ttest_sgeno_cgent_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgent_large_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ gen, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgeno_csockf_small_tcp4(suite) ->
+ [];
+ttest_sgeno_csockf_small_tcp4(doc) ->
+ [];
+ttest_sgeno_csockf_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csockf_small_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ sock, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgeno_csockf_small_tcp6(suite) ->
+ [];
+ttest_sgeno_csockf_small_tcp6(doc) ->
+ [];
+ttest_sgeno_csockf_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csockf_small_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ sock, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgeno_csockf_medium_tcp4(suite) ->
+ [];
+ttest_sgeno_csockf_medium_tcp4(doc) ->
+ [];
+ttest_sgeno_csockf_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csockf_medium_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ sock, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgeno_csockf_medium_tcp6(suite) ->
+ [];
+ttest_sgeno_csockf_medium_tcp6(doc) ->
+ [];
+ttest_sgeno_csockf_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csockf_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ sock, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgeno_csockf_large_tcp4(suite) ->
+ [];
+ttest_sgeno_csockf_large_tcp4(doc) ->
+ [];
+ttest_sgeno_csockf_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csockf_large_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ sock, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgeno_csockf_large_tcp6(suite) ->
+ [];
+ttest_sgeno_csockf_large_tcp6(doc) ->
+ [];
+ttest_sgeno_csockf_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csockf_large_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ sock, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgeno_csocko_small_tcp4(suite) ->
+ [];
+ttest_sgeno_csocko_small_tcp4(doc) ->
+ [];
+ttest_sgeno_csocko_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csocko_small_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ sock, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgeno_csocko_small_tcp6(suite) ->
+ [];
+ttest_sgeno_csocko_small_tcp6(doc) ->
+ [];
+ttest_sgeno_csocko_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csocko_small_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ sock, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgeno_csocko_medium_tcp4(suite) ->
+ [];
+ttest_sgeno_csocko_medium_tcp4(doc) ->
+ [];
+ttest_sgeno_csocko_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csocko_medium_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ sock, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgeno_csocko_medium_tcp6(suite) ->
+ [];
+ttest_sgeno_csocko_medium_tcp6(doc) ->
+ [];
+ttest_sgeno_csocko_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csocko_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ sock, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgeno_csocko_large_tcp4(suite) ->
+ [];
+ttest_sgeno_csocko_large_tcp4(doc) ->
+ [];
+ttest_sgeno_csocko_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csocko_large_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ sock, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgeno_csocko_large_tcp6(suite) ->
+ [];
+ttest_sgeno_csocko_large_tcp6(doc) ->
+ [];
+ttest_sgeno_csocko_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csocko_large_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ sock, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgeno_csockt_small_tcp4(suite) ->
+ [];
+ttest_sgeno_csockt_small_tcp4(doc) ->
+ [];
+ttest_sgeno_csockt_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csockt_small_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ sock, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgeno_csockt_small_tcp6(suite) ->
+ [];
+ttest_sgeno_csockt_small_tcp6(doc) ->
+ [];
+ttest_sgeno_csockt_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csocko_small_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ sock, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgeno_csockt_medium_tcp4(suite) ->
+ [];
+ttest_sgeno_csockt_medium_tcp4(doc) ->
+ [];
+ttest_sgeno_csockt_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csockt_medium_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ sock, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgeno_csockt_medium_tcp6(suite) ->
+ [];
+ttest_sgeno_csockt_medium_tcp6(doc) ->
+ [];
+ttest_sgeno_csockt_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csockt_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ sock, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgeno_csockt_large_tcp4(suite) ->
+ [];
+ttest_sgeno_csockt_large_tcp4(doc) ->
+ [];
+ttest_sgeno_csockt_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csockt_large_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ sock, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgeno_csockt_large_tcp6(suite) ->
+ [];
+ttest_sgeno_csockt_large_tcp6(doc) ->
+ [];
+ttest_sgeno_csockt_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csockt_large_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ sock, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgent_cgenf_small_tcp4(suite) ->
+ [];
+ttest_sgent_cgenf_small_tcp4(doc) ->
+ [];
+ttest_sgent_cgenf_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgenf_small_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ gen, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgent_cgenf_small_tcp6(suite) ->
+ [];
+ttest_sgent_cgenf_small_tcp6(doc) ->
+ [];
+ttest_sgent_cgenf_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgenf_small_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ gen, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgent_cgenf_medium_tcp4(suite) ->
+ [];
+ttest_sgent_cgenf_medium_tcp4(doc) ->
+ [];
+ttest_sgent_cgenf_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgenf_medium_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ gen, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgent_cgenf_medium_tcp6(suite) ->
+ [];
+ttest_sgent_cgenf_medium_tcp6(doc) ->
+ [];
+ttest_sgent_cgenf_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgenf_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ gen, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgent_cgenf_large_tcp4(suite) ->
+ [];
+ttest_sgent_cgenf_large_tcp4(doc) ->
+ [];
+ttest_sgent_cgenf_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgenf_large_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ gen, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgent_cgenf_large_tcp6(suite) ->
+ [];
+ttest_sgent_cgenf_large_tcp6(doc) ->
+ [];
+ttest_sgent_cgenf_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgenf_large_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ gen, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgent_cgeno_small_tcp4(suite) ->
+ [];
+ttest_sgent_cgeno_small_tcp4(doc) ->
+ [];
+ttest_sgent_cgeno_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgeno_small_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ gen, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgent_cgeno_small_tcp6(suite) ->
+ [];
+ttest_sgent_cgeno_small_tcp6(doc) ->
+ [];
+ttest_sgent_cgeno_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgeno_small_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ gen, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgent_cgeno_medium_tcp4(suite) ->
+ [];
+ttest_sgent_cgeno_medium_tcp4(doc) ->
+ [];
+ttest_sgent_cgeno_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgeno_medium_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ gen, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgent_cgeno_medium_tcp6(suite) ->
+ [];
+ttest_sgent_cgeno_medium_tcp6(doc) ->
+ [];
+ttest_sgent_cgeno_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgeno_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ gen, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgent_cgeno_large_tcp4(suite) ->
+ [];
+ttest_sgent_cgeno_large_tcp4(doc) ->
+ [];
+ttest_sgent_cgeno_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgeno_large_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ gen, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgent_cgeno_large_tcp6(suite) ->
+ [];
+ttest_sgent_cgeno_large_tcp6(doc) ->
+ [];
+ttest_sgent_cgeno_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgeno_large_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ gen, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgent_cgent_small_tcp4(suite) ->
+ [];
+ttest_sgent_cgent_small_tcp4(doc) ->
+ [];
+ttest_sgent_cgent_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgent_small_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ gen, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgent_cgent_small_tcp6(suite) ->
+ [];
+ttest_sgent_cgent_small_tcp6(doc) ->
+ [];
+ttest_sgent_cgent_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgeno_small_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ gen, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgent_cgent_medium_tcp4(suite) ->
+ [];
+ttest_sgent_cgent_medium_tcp4(doc) ->
+ ["Server(gen,true), Client(gen,true), Domain=inet, msg=medium"];
+ttest_sgent_cgent_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgent_medium_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ gen, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgent_cgent_medium_tcp6(suite) ->
+ [];
+ttest_sgent_cgent_medium_tcp6(doc) ->
+ ["Server(gen,true), Client(gen,true), Domain=inet6, msg=medium"];
+ttest_sgent_cgent_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgent_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ gen, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgent_cgent_large_tcp4(suite) ->
+ [];
+ttest_sgent_cgent_large_tcp4(doc) ->
+ ["Server(gen,true), Client(gen,true), Domain=inet, msg=large"];
+ttest_sgent_cgent_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgent_large_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ gen, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgent_cgent_large_tcp6(suite) ->
+ [];
+ttest_sgent_cgent_large_tcp6(doc) ->
+ ["Server(gen,true), Client(gen,true), Domain=inet6, msg=large"];
+ttest_sgent_cgent_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgent_large_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ gen, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgent_csockf_small_tcp4(suite) ->
+ [];
+ttest_sgent_csockf_small_tcp4(doc) ->
+ [];
+ttest_sgent_csockf_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csockf_small_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ sock, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgent_csockf_small_tcp6(suite) ->
+ [];
+ttest_sgent_csockf_small_tcp6(doc) ->
+ [];
+ttest_sgent_csockf_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csockf_small_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ sock, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgent_csockf_medium_tcp4(suite) ->
+ [];
+ttest_sgent_csockf_medium_tcp4(doc) ->
+ [];
+ttest_sgent_csockf_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csockf_medium_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ sock, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgent_csockf_medium_tcp6(suite) ->
+ [];
+ttest_sgent_csockf_medium_tcp6(doc) ->
+ [];
+ttest_sgent_csockf_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csockf_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ sock, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgent_csockf_large_tcp4(suite) ->
+ [];
+ttest_sgent_csockf_large_tcp4(doc) ->
+ [];
+ttest_sgent_csockf_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csockf_large_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ sock, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgent_csockf_large_tcp6(suite) ->
+ [];
+ttest_sgent_csockf_large_tcp6(doc) ->
+ [];
+ttest_sgent_csockf_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csockf_large_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ sock, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgent_csocko_small_tcp4(suite) ->
+ [];
+ttest_sgent_csocko_small_tcp4(doc) ->
+ [];
+ttest_sgent_csocko_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csocko_small_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ sock, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgent_csocko_small_tcp6(suite) ->
+ [];
+ttest_sgent_csocko_small_tcp6(doc) ->
+ [];
+ttest_sgent_csocko_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csocko_small_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ sock, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgent_csocko_medium_tcp4(suite) ->
+ [];
+ttest_sgent_csocko_medium_tcp4(doc) ->
+ [];
+ttest_sgent_csocko_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csocko_medium_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ sock, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgent_csocko_medium_tcp6(suite) ->
+ [];
+ttest_sgent_csocko_medium_tcp6(doc) ->
+ [];
+ttest_sgent_csocko_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csocko_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ sock, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgent_csocko_large_tcp4(suite) ->
+ [];
+ttest_sgent_csocko_large_tcp4(doc) ->
+ [];
+ttest_sgent_csocko_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csocko_large_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ sock, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgent_csocko_large_tcp6(suite) ->
+ [];
+ttest_sgent_csocko_large_tcp6(doc) ->
+ [];
+ttest_sgent_csocko_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csocko_large_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ sock, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgent_csockt_small_tcp4(suite) ->
+ [];
+ttest_sgent_csockt_small_tcp4(doc) ->
+ [];
+ttest_sgent_csockt_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csockt_small_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ sock, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgent_csockt_small_tcp6(suite) ->
+ [];
+ttest_sgent_csockt_small_tcp6(doc) ->
+ [];
+ttest_sgent_csockt_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csocko_small_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ sock, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgent_csockt_medium_tcp4(suite) ->
+ [];
+ttest_sgent_csockt_medium_tcp4(doc) ->
+ [];
+ttest_sgent_csockt_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csockt_medium_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ sock, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgent_csockt_medium_tcp6(suite) ->
+ [];
+ttest_sgent_csockt_medium_tcp6(doc) ->
+ [];
+ttest_sgent_csockt_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csockt_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ sock, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgent_csockt_large_tcp4(suite) ->
+ [];
+ttest_sgent_csockt_large_tcp4(doc) ->
+ [];
+ttest_sgent_csockt_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csockt_large_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ sock, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgent_csockt_large_tcp6(suite) ->
+ [];
+ttest_sgent_csockt_large_tcp6(doc) ->
+ [];
+ttest_sgent_csockt_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csockt_large_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ sock, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssockf_cgenf_small_tcp4(suite) ->
+ [];
+ttest_ssockf_cgenf_small_tcp4(doc) ->
+ [];
+ttest_ssockf_cgenf_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgenf_small_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ gen, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssockf_cgenf_small_tcp6(suite) ->
+ [];
+ttest_ssockf_cgenf_small_tcp6(doc) ->
+ [];
+ttest_ssockf_cgenf_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgenf_small_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ gen, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssockf_cgenf_medium_tcp4(suite) ->
+ [];
+ttest_ssockf_cgenf_medium_tcp4(doc) ->
+ [];
+ttest_ssockf_cgenf_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgenf_medium_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ gen, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssockf_cgenf_medium_tcp6(suite) ->
+ [];
+ttest_ssockf_cgenf_medium_tcp6(doc) ->
+ [];
+ttest_ssockf_cgenf_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgenf_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ gen, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssockf_cgenf_large_tcp4(suite) ->
+ [];
+ttest_ssockf_cgenf_large_tcp4(doc) ->
+ [];
+ttest_ssockf_cgenf_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgenf_large_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ gen, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssockf_cgenf_large_tcp6(suite) ->
+ [];
+ttest_ssockf_cgenf_large_tcp6(doc) ->
+ [];
+ttest_ssockf_cgenf_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgenf_large_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ gen, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssockf_cgeno_small_tcp4(suite) ->
+ [];
+ttest_ssockf_cgeno_small_tcp4(doc) ->
+ [];
+ttest_ssockf_cgeno_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgeno_small_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ gen, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssockf_cgeno_small_tcp6(suite) ->
+ [];
+ttest_ssockf_cgeno_small_tcp6(doc) ->
+ [];
+ttest_ssockf_cgeno_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgeno_small_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ gen, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssockf_cgeno_medium_tcp4(suite) ->
+ [];
+ttest_ssockf_cgeno_medium_tcp4(doc) ->
+ [];
+ttest_ssockf_cgeno_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgeno_medium_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ gen, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssockf_cgeno_medium_tcp6(suite) ->
+ [];
+ttest_ssockf_cgeno_medium_tcp6(doc) ->
+ [];
+ttest_ssockf_cgeno_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgeno_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ gen, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssockf_cgeno_large_tcp4(suite) ->
+ [];
+ttest_ssockf_cgeno_large_tcp4(doc) ->
+ [];
+ttest_ssockf_cgeno_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgeno_large_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ gen, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssockf_cgeno_large_tcp6(suite) ->
+ [];
+ttest_ssockf_cgeno_large_tcp6(doc) ->
+ [];
+ttest_ssockf_cgeno_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgeno_large_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ gen, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssockf_cgent_small_tcp4(suite) ->
+ [];
+ttest_ssockf_cgent_small_tcp4(doc) ->
+ [];
+ttest_ssockf_cgent_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgent_small_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ gen, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssockf_cgent_small_tcp6(suite) ->
+ [];
+ttest_ssockf_cgent_small_tcp6(doc) ->
+ [];
+ttest_ssockf_cgent_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgeno_small_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ gen, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssockf_cgent_medium_tcp4(suite) ->
+ [];
+ttest_ssockf_cgent_medium_tcp4(doc) ->
+ [];
+ttest_ssockf_cgent_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgent_medium_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ gen, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssockf_cgent_medium_tcp6(suite) ->
+ [];
+ttest_ssockf_cgent_medium_tcp6(doc) ->
+ [];
+ttest_ssockf_cgent_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgent_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ gen, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssockf_cgent_large_tcp4(suite) ->
+ [];
+ttest_ssockf_cgent_large_tcp4(doc) ->
+ [];
+ttest_ssockf_cgent_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgent_large_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ gen, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssockf_cgent_large_tcp6(suite) ->
+ [];
+ttest_ssockf_cgent_large_tcp6(doc) ->
+ [];
+ttest_ssockf_cgent_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgent_large_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ gen, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssockf_csockf_small_tcp4(suite) ->
+ [];
+ttest_ssockf_csockf_small_tcp4(doc) ->
+ [];
+ttest_ssockf_csockf_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csockf_small_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ sock, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssockf_csockf_small_tcp6(suite) ->
+ [];
+ttest_ssockf_csockf_small_tcp6(doc) ->
+ [];
+ttest_ssockf_csockf_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csockf_small_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ sock, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: small (=1)
+%% Domain: local
+%%
+
+ttest_ssockf_csockf_small_tcpL(suite) ->
+ [];
+ttest_ssockf_csockf_small_tcpL(doc) ->
+ [];
+ttest_ssockf_csockf_small_tcpL(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csockf_small_tcpL,
+ Runtime,
+ local,
+ sock, false,
+ sock, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssockf_csockf_medium_tcp4(suite) ->
+ [];
+ttest_ssockf_csockf_medium_tcp4(doc) ->
+ [];
+ttest_ssockf_csockf_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csockf_medium_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ sock, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssockf_csockf_medium_tcp6(suite) ->
+ [];
+ttest_ssockf_csockf_medium_tcp6(doc) ->
+ [];
+ttest_ssockf_csockf_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csockf_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ sock, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: medium (=2)
+%% Domain: local
+%%
+
+ttest_ssockf_csockf_medium_tcpL(suite) ->
+ [];
+ttest_ssockf_csockf_medium_tcpL(doc) ->
+ [];
+ttest_ssockf_csockf_medium_tcpL(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csockf_medium_tcpL,
+ Runtime,
+ local,
+ sock, false,
+ sock, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssockf_csockf_large_tcp4(suite) ->
+ [];
+ttest_ssockf_csockf_large_tcp4(doc) ->
+ [];
+ttest_ssockf_csockf_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csockf_large_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ sock, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssockf_csockf_large_tcp6(suite) ->
+ [];
+ttest_ssockf_csockf_large_tcp6(doc) ->
+ [];
+ttest_ssockf_csockf_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csockf_large_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ sock, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: large (=3)
+%% Domain: local
+%%
+
+ttest_ssockf_csockf_large_tcpL(suite) ->
+ [];
+ttest_ssockf_csockf_large_tcpL(doc) ->
+ [];
+ttest_ssockf_csockf_large_tcpL(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csockf_large_tcpL,
+ Runtime,
+ local,
+ sock, false,
+ sock, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssockf_csocko_small_tcp4(suite) ->
+ [];
+ttest_ssockf_csocko_small_tcp4(doc) ->
+ [];
+ttest_ssockf_csocko_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csocko_small_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ sock, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssockf_csocko_small_tcp6(suite) ->
+ [];
+ttest_ssockf_csocko_small_tcp6(doc) ->
+ [];
+ttest_ssockf_csocko_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csocko_small_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ sock, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: small (=1)
+%% Domain: local
+%%
+
+ttest_ssockf_csocko_small_tcpL(suite) ->
+ [];
+ttest_ssockf_csocko_small_tcpL(doc) ->
+ [];
+ttest_ssockf_csocko_small_tcpL(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csocko_small_tcpL,
+ Runtime,
+ local,
+ sock, false,
+ sock, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssockf_csocko_medium_tcp4(suite) ->
+ [];
+ttest_ssockf_csocko_medium_tcp4(doc) ->
+ [];
+ttest_ssockf_csocko_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csocko_medium_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ sock, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssockf_csocko_medium_tcp6(suite) ->
+ [];
+ttest_ssockf_csocko_medium_tcp6(doc) ->
+ [];
+ttest_ssockf_csocko_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csocko_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ sock, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: medium (=2)
+%% Domain: local
+%%
+
+ttest_ssockf_csocko_medium_tcpL(suite) ->
+ [];
+ttest_ssockf_csocko_medium_tcpL(doc) ->
+ [];
+ttest_ssockf_csocko_medium_tcpL(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csocko_medium_tcpL,
+ Runtime,
+ local,
+ sock, false,
+ sock, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssockf_csocko_large_tcp4(suite) ->
+ [];
+ttest_ssockf_csocko_large_tcp4(doc) ->
+ [];
+ttest_ssockf_csocko_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csocko_large_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ sock, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssockf_csocko_large_tcp6(suite) ->
+ [];
+ttest_ssockf_csocko_large_tcp6(doc) ->
+ [];
+ttest_ssockf_csocko_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csocko_large_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ sock, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: large (=3)
+%% Domain: local
+%%
+
+ttest_ssockf_csocko_large_tcpL(suite) ->
+ [];
+ttest_ssockf_csocko_large_tcpL(doc) ->
+ [];
+ttest_ssockf_csocko_large_tcpL(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csocko_large_tcpL,
+ Runtime,
+ local,
+ sock, false,
+ sock, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssockf_csockt_small_tcp4(suite) ->
+ [];
+ttest_ssockf_csockt_small_tcp4(doc) ->
+ [];
+ttest_ssockf_csockt_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csockt_small_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ sock, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssockf_csockt_small_tcp6(suite) ->
+ [];
+ttest_ssockf_csockt_small_tcp6(doc) ->
+ [];
+ttest_ssockf_csockt_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csocko_small_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ sock, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: small (=1)
+%% Domain: local
+%%
+
+ttest_ssockf_csockt_small_tcpL(suite) ->
+ [];
+ttest_ssockf_csockt_small_tcpL(doc) ->
+ [];
+ttest_ssockf_csockt_small_tcpL(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csocko_small_tcpL,
+ Runtime,
+ local,
+ sock, false,
+ sock, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssockf_csockt_medium_tcp4(suite) ->
+ [];
+ttest_ssockf_csockt_medium_tcp4(doc) ->
+ [];
+ttest_ssockf_csockt_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csockt_medium_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ sock, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssockf_csockt_medium_tcp6(suite) ->
+ [];
+ttest_ssockf_csockt_medium_tcp6(doc) ->
+ [];
+ttest_ssockf_csockt_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csockt_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ sock, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: medium (=2)
+%% Domain: local
+%%
+
+ttest_ssockf_csockt_medium_tcpL(suite) ->
+ [];
+ttest_ssockf_csockt_medium_tcpL(doc) ->
+ [];
+ttest_ssockf_csockt_medium_tcpL(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csockt_medium_tcpL,
+ Runtime,
+ local,
+ sock, false,
+ sock, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssockf_csockt_large_tcp4(suite) ->
+ [];
+ttest_ssockf_csockt_large_tcp4(doc) ->
+ [];
+ttest_ssockf_csockt_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csockt_large_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ sock, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssockf_csockt_large_tcp6(suite) ->
+ [];
+ttest_ssockf_csockt_large_tcp6(doc) ->
+ [];
+ttest_ssockf_csockt_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csockt_large_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ sock, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: large (=3)
+%% Domain: local
+%%
+
+ttest_ssockf_csockt_large_tcpL(suite) ->
+ [];
+ttest_ssockf_csockt_large_tcpL(doc) ->
+ [];
+ttest_ssockf_csockt_large_tcpL(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csockt_large_tcpL,
+ Runtime,
+ local,
+ sock, false,
+ sock, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssocko_cgenf_small_tcp4(suite) ->
+ [];
+ttest_ssocko_cgenf_small_tcp4(doc) ->
+ [];
+ttest_ssocko_cgenf_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgenf_small_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ gen, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssocko_cgenf_small_tcp6(suite) ->
+ [];
+ttest_ssocko_cgenf_small_tcp6(doc) ->
+ [];
+ttest_ssocko_cgenf_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgenf_small_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ gen, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssocko_cgenf_medium_tcp4(suite) ->
+ [];
+ttest_ssocko_cgenf_medium_tcp4(doc) ->
+ [];
+ttest_ssocko_cgenf_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgenf_medium_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ gen, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssocko_cgenf_medium_tcp6(suite) ->
+ [];
+ttest_ssocko_cgenf_medium_tcp6(doc) ->
+ [];
+ttest_ssocko_cgenf_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgenf_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ gen, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssocko_cgenf_large_tcp4(suite) ->
+ [];
+ttest_ssocko_cgenf_large_tcp4(doc) ->
+ [];
+ttest_ssocko_cgenf_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgenf_large_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ gen, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssocko_cgenf_large_tcp6(suite) ->
+ [];
+ttest_ssocko_cgenf_large_tcp6(doc) ->
+ [];
+ttest_ssocko_cgenf_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgenf_large_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ gen, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssocko_cgeno_small_tcp4(suite) ->
+ [];
+ttest_ssocko_cgeno_small_tcp4(doc) ->
+ [];
+ttest_ssocko_cgeno_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgeno_small_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ gen, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssocko_cgeno_small_tcp6(suite) ->
+ [];
+ttest_ssocko_cgeno_small_tcp6(doc) ->
+ [];
+ttest_ssocko_cgeno_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgeno_small_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ gen, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssocko_cgeno_medium_tcp4(suite) ->
+ [];
+ttest_ssocko_cgeno_medium_tcp4(doc) ->
+ [];
+ttest_ssocko_cgeno_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgeno_medium_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ gen, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssocko_cgeno_medium_tcp6(suite) ->
+ [];
+ttest_ssocko_cgeno_medium_tcp6(doc) ->
+ [];
+ttest_ssocko_cgeno_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgeno_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ gen, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssocko_cgeno_large_tcp4(suite) ->
+ [];
+ttest_ssocko_cgeno_large_tcp4(doc) ->
+ [];
+ttest_ssocko_cgeno_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgeno_large_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ gen, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssocko_cgeno_large_tcp6(suite) ->
+ [];
+ttest_ssocko_cgeno_large_tcp6(doc) ->
+ [];
+ttest_ssocko_cgeno_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgeno_large_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ gen, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssocko_cgent_small_tcp4(suite) ->
+ [];
+ttest_ssocko_cgent_small_tcp4(doc) ->
+ [];
+ttest_ssocko_cgent_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgent_small_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ gen, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssocko_cgent_small_tcp6(suite) ->
+ [];
+ttest_ssocko_cgent_small_tcp6(doc) ->
+ [];
+ttest_ssocko_cgent_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgent_small_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ gen, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssocko_cgent_medium_tcp4(suite) ->
+ [];
+ttest_ssocko_cgent_medium_tcp4(doc) ->
+ [];
+ttest_ssocko_cgent_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgent_medium_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ gen, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssocko_cgent_medium_tcp6(suite) ->
+ [];
+ttest_ssocko_cgent_medium_tcp6(doc) ->
+ [];
+ttest_ssocko_cgent_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgent_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ gen, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssocko_cgent_large_tcp4(suite) ->
+ [];
+ttest_ssocko_cgent_large_tcp4(doc) ->
+ [];
+ttest_ssocko_cgent_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgent_large_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ gen, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssocko_cgent_large_tcp6(suite) ->
+ [];
+ttest_ssocko_cgent_large_tcp6(doc) ->
+ [];
+ttest_ssocko_cgent_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgent_large_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ gen, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssocko_csockf_small_tcp4(suite) ->
+ [];
+ttest_ssocko_csockf_small_tcp4(doc) ->
+ [];
+ttest_ssocko_csockf_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csockf_small_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ sock, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssocko_csockf_small_tcp6(suite) ->
+ [];
+ttest_ssocko_csockf_small_tcp6(doc) ->
+ [];
+ttest_ssocko_csockf_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csockf_small_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ sock, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: small (=1)
+%% Domain: local
+%%
+
+ttest_ssocko_csockf_small_tcpL(suite) ->
+ [];
+ttest_ssocko_csockf_small_tcpL(doc) ->
+ [];
+ttest_ssocko_csockf_small_tcpL(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csockf_small_tcpL,
+ Runtime,
+ local,
+ sock, once,
+ sock, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssocko_csockf_medium_tcp4(suite) ->
+ [];
+ttest_ssocko_csockf_medium_tcp4(doc) ->
+ [];
+ttest_ssocko_csockf_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csockf_medium_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ sock, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssocko_csockf_medium_tcp6(suite) ->
+ [];
+ttest_ssocko_csockf_medium_tcp6(doc) ->
+ [];
+ttest_ssocko_csockf_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csockf_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ sock, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: medium (=2)
+%% Domain: local
+%%
+
+ttest_ssocko_csockf_medium_tcpL(suite) ->
+ [];
+ttest_ssocko_csockf_medium_tcpL(doc) ->
+ [];
+ttest_ssocko_csockf_medium_tcpL(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csockf_medium_tcpL,
+ Runtime,
+ local,
+ sock, once,
+ sock, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssocko_csockf_large_tcp4(suite) ->
+ [];
+ttest_ssocko_csockf_large_tcp4(doc) ->
+ [];
+ttest_ssocko_csockf_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csockf_large_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ sock, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssocko_csockf_large_tcp6(suite) ->
+ [];
+ttest_ssocko_csockf_large_tcp6(doc) ->
+ [];
+ttest_ssocko_csockf_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csockf_large_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ sock, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: large (=3)
+%% Domain: local
+%%
+
+ttest_ssocko_csockf_large_tcpL(suite) ->
+ [];
+ttest_ssocko_csockf_large_tcpL(doc) ->
+ [];
+ttest_ssocko_csockf_large_tcpL(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csockf_large_tcpL,
+ Runtime,
+ local,
+ sock, once,
+ sock, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssocko_csocko_small_tcp4(suite) ->
+ [];
+ttest_ssocko_csocko_small_tcp4(doc) ->
+ [];
+ttest_ssocko_csocko_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csocko_small_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ sock, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssocko_csocko_small_tcp6(suite) ->
+ [];
+ttest_ssocko_csocko_small_tcp6(doc) ->
+ [];
+ttest_ssocko_csocko_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csocko_small_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ sock, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: small (=1)
+%% Domain: local
+%%
+
+ttest_ssocko_csocko_small_tcpL(suite) ->
+ [];
+ttest_ssocko_csocko_small_tcpL(doc) ->
+ [];
+ttest_ssocko_csocko_small_tcpL(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csocko_small_tcpL,
+ Runtime,
+ local,
+ sock, once,
+ sock, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssocko_csocko_medium_tcp4(suite) ->
+ [];
+ttest_ssocko_csocko_medium_tcp4(doc) ->
+ [];
+ttest_ssocko_csocko_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csocko_medium_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ sock, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssocko_csocko_medium_tcp6(suite) ->
+ [];
+ttest_ssocko_csocko_medium_tcp6(doc) ->
+ [];
+ttest_ssocko_csocko_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csocko_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ sock, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: medium (=2)
+%% Domain: local
+%%
+
+ttest_ssocko_csocko_medium_tcpL(suite) ->
+ [];
+ttest_ssocko_csocko_medium_tcpL(doc) ->
+ [];
+ttest_ssocko_csocko_medium_tcpL(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csocko_medium_tcpL,
+ Runtime,
+ local,
+ sock, once,
+ sock, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssocko_csocko_large_tcp4(suite) ->
+ [];
+ttest_ssocko_csocko_large_tcp4(doc) ->
+ [];
+ttest_ssocko_csocko_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csocko_large_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ sock, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssocko_csocko_large_tcp6(suite) ->
+ [];
+ttest_ssocko_csocko_large_tcp6(doc) ->
+ [];
+ttest_ssocko_csocko_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csocko_large_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ sock, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: large (=3)
+%% Domain: local
+%%
+
+ttest_ssocko_csocko_large_tcpL(suite) ->
+ [];
+ttest_ssocko_csocko_large_tcpL(doc) ->
+ [];
+ttest_ssocko_csocko_large_tcpL(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csocko_large_tcpL,
+ Runtime,
+ local,
+ sock, once,
+ sock, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssocko_csockt_small_tcp4(suite) ->
+ [];
+ttest_ssocko_csockt_small_tcp4(doc) ->
+ [];
+ttest_ssocko_csockt_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csockt_small_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ sock, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssocko_csockt_small_tcp6(suite) ->
+ [];
+ttest_ssocko_csockt_small_tcp6(doc) ->
+ [];
+ttest_ssocko_csockt_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csocko_small_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ sock, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: small (=1)
+%% Domain: local
+%%
+
+ttest_ssocko_csockt_small_tcpL(suite) ->
+ [];
+ttest_ssocko_csockt_small_tcpL(doc) ->
+ [];
+ttest_ssocko_csockt_small_tcpL(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csocko_small_tcpL,
+ Runtime,
+ local,
+ sock, once,
+ sock, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssocko_csockt_medium_tcp4(suite) ->
+ [];
+ttest_ssocko_csockt_medium_tcp4(doc) ->
+ [];
+ttest_ssocko_csockt_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csockt_medium_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ sock, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssocko_csockt_medium_tcp6(suite) ->
+ [];
+ttest_ssocko_csockt_medium_tcp6(doc) ->
+ [];
+ttest_ssocko_csockt_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csockt_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ sock, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: medium (=2)
+%% Domain: local
+%%
+
+ttest_ssocko_csockt_medium_tcpL(suite) ->
+ [];
+ttest_ssocko_csockt_medium_tcpL(doc) ->
+ [];
+ttest_ssocko_csockt_medium_tcpL(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csockt_medium_tcpL,
+ Runtime,
+ local,
+ sock, once,
+ sock, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssocko_csockt_large_tcp4(suite) ->
+ [];
+ttest_ssocko_csockt_large_tcp4(doc) ->
+ [];
+ttest_ssocko_csockt_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csockt_large_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ sock, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssocko_csockt_large_tcp6(suite) ->
+ [];
+ttest_ssocko_csockt_large_tcp6(doc) ->
+ [];
+ttest_ssocko_csockt_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csockt_large_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ sock, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: large (=3)
+%% Domain: local
+%%
+
+ttest_ssocko_csockt_large_tcpL(suite) ->
+ [];
+ttest_ssocko_csockt_large_tcpL(doc) ->
+ [];
+ttest_ssocko_csockt_large_tcpL(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csockt_large_tcpL,
+ Runtime,
+ local,
+ sock, once,
+ sock, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssockt_cgenf_small_tcp4(suite) ->
+ [];
+ttest_ssockt_cgenf_small_tcp4(doc) ->
+ [];
+ttest_ssockt_cgenf_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgenf_small_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ gen, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssockt_cgenf_small_tcp6(suite) ->
+ [];
+ttest_ssockt_cgenf_small_tcp6(doc) ->
+ [];
+ttest_ssockt_cgenf_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgenf_small_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ gen, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssockt_cgenf_medium_tcp4(suite) ->
+ [];
+ttest_ssockt_cgenf_medium_tcp4(doc) ->
+ [];
+ttest_ssockt_cgenf_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgenf_medium_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ gen, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssockt_cgenf_medium_tcp6(suite) ->
+ [];
+ttest_ssockt_cgenf_medium_tcp6(doc) ->
+ [];
+ttest_ssockt_cgenf_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgenf_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ gen, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssockt_cgenf_large_tcp4(suite) ->
+ [];
+ttest_ssockt_cgenf_large_tcp4(doc) ->
+ [];
+ttest_ssockt_cgenf_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgenf_large_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ gen, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssockt_cgenf_large_tcp6(suite) ->
+ [];
+ttest_ssockt_cgenf_large_tcp6(doc) ->
+ [];
+ttest_ssockt_cgenf_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgenf_large_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ gen, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssockt_cgeno_small_tcp4(suite) ->
+ [];
+ttest_ssockt_cgeno_small_tcp4(doc) ->
+ [];
+ttest_ssockt_cgeno_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgeno_small_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ gen, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssockt_cgeno_small_tcp6(suite) ->
+ [];
+ttest_ssockt_cgeno_small_tcp6(doc) ->
+ [];
+ttest_ssockt_cgeno_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgeno_small_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ gen, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssockt_cgeno_medium_tcp4(suite) ->
+ [];
+ttest_ssockt_cgeno_medium_tcp4(doc) ->
+ [];
+ttest_ssockt_cgeno_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgeno_medium_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ gen, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssockt_cgeno_medium_tcp6(suite) ->
+ [];
+ttest_ssockt_cgeno_medium_tcp6(doc) ->
+ [];
+ttest_ssockt_cgeno_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgeno_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ gen, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssockt_cgeno_large_tcp4(suite) ->
+ [];
+ttest_ssockt_cgeno_large_tcp4(doc) ->
+ [];
+ttest_ssockt_cgeno_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgeno_large_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ gen, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssockt_cgeno_large_tcp6(suite) ->
+ [];
+ttest_ssockt_cgeno_large_tcp6(doc) ->
+ [];
+ttest_ssockt_cgeno_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgeno_large_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ gen, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssockt_cgent_small_tcp4(suite) ->
+ [];
+ttest_ssockt_cgent_small_tcp4(doc) ->
+ [];
+ttest_ssockt_cgent_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgent_small_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ gen, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssockt_cgent_small_tcp6(suite) ->
+ [];
+ttest_ssockt_cgent_small_tcp6(doc) ->
+ [];
+ttest_ssockt_cgent_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgent_small_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ gen, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssockt_cgent_medium_tcp4(suite) ->
+ [];
+ttest_ssockt_cgent_medium_tcp4(doc) ->
+ [];
+ttest_ssockt_cgent_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgent_medium_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ gen, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssockt_cgent_medium_tcp6(suite) ->
+ [];
+ttest_ssockt_cgent_medium_tcp6(doc) ->
+ [];
+ttest_ssockt_cgent_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgent_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ gen, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssockt_cgent_large_tcp4(suite) ->
+ [];
+ttest_ssockt_cgent_large_tcp4(doc) ->
+ [];
+ttest_ssockt_cgent_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgent_large_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ gen, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssockt_cgent_large_tcp6(suite) ->
+ [];
+ttest_ssockt_cgent_large_tcp6(doc) ->
+ [];
+ttest_ssockt_cgent_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgent_large_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ gen, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssockt_csockf_small_tcp4(suite) ->
+ [];
+ttest_ssockt_csockf_small_tcp4(doc) ->
+ [];
+ttest_ssockt_csockf_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csockf_small_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ sock, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssockt_csockf_small_tcp6(suite) ->
+ [];
+ttest_ssockt_csockf_small_tcp6(doc) ->
+ [];
+ttest_ssockt_csockf_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csockf_small_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ sock, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: small (=1)
+%% Domain: local
+%%
+
+ttest_ssockt_csockf_small_tcpL(suite) ->
+ [];
+ttest_ssockt_csockf_small_tcpL(doc) ->
+ [];
+ttest_ssockt_csockf_small_tcpL(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csockf_small_tcpL,
+ Runtime,
+ local,
+ sock, true,
+ sock, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssockt_csockf_medium_tcp4(suite) ->
+ [];
+ttest_ssockt_csockf_medium_tcp4(doc) ->
+ [];
+ttest_ssockt_csockf_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csockf_medium_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ sock, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssockt_csockf_medium_tcp6(suite) ->
+ [];
+ttest_ssockt_csockf_medium_tcp6(doc) ->
+ [];
+ttest_ssockt_csockf_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csockf_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ sock, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: medium (=2)
+%% Domain: local
+%%
+
+ttest_ssockt_csockf_medium_tcpL(suite) ->
+ [];
+ttest_ssockt_csockf_medium_tcpL(doc) ->
+ [];
+ttest_ssockt_csockf_medium_tcpL(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csockf_medium_tcpL,
+ Runtime,
+ local,
+ sock, true,
+ sock, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssockt_csockf_large_tcp4(suite) ->
+ [];
+ttest_ssockt_csockf_large_tcp4(doc) ->
+ [];
+ttest_ssockt_csockf_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csockf_large_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ sock, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssockt_csockf_large_tcp6(suite) ->
+ [];
+ttest_ssockt_csockf_large_tcp6(doc) ->
+ [];
+ttest_ssockt_csockf_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csockf_large_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ sock, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: large (=3)
+%% Domain: local
+%%
+
+ttest_ssockt_csockf_large_tcpL(suite) ->
+ [];
+ttest_ssockt_csockf_large_tcpL(doc) ->
+ [];
+ttest_ssockt_csockf_large_tcpL(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csockf_large_tcpL,
+ Runtime,
+ local,
+ sock, true,
+ sock, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssockt_csocko_small_tcp4(suite) ->
+ [];
+ttest_ssockt_csocko_small_tcp4(doc) ->
+ [];
+ttest_ssockt_csocko_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csocko_small_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ sock, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssockt_csocko_small_tcp6(suite) ->
+ [];
+ttest_ssockt_csocko_small_tcp6(doc) ->
+ [];
+ttest_ssockt_csocko_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csocko_small_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ sock, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: small (=1)
+%% Domain: local
+%%
+
+ttest_ssockt_csocko_small_tcpL(suite) ->
+ [];
+ttest_ssockt_csocko_small_tcpL(doc) ->
+ [];
+ttest_ssockt_csocko_small_tcpL(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csocko_small_tcpL,
+ Runtime,
+ local,
+ sock, true,
+ sock, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssockt_csocko_medium_tcp4(suite) ->
+ [];
+ttest_ssockt_csocko_medium_tcp4(doc) ->
+ [];
+ttest_ssockt_csocko_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csocko_medium_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ sock, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssockt_csocko_medium_tcp6(suite) ->
+ [];
+ttest_ssockt_csocko_medium_tcp6(doc) ->
+ [];
+ttest_ssockt_csocko_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csocko_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ sock, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: medium (=2)
+%% Domain: local
+%%
+
+ttest_ssockt_csocko_medium_tcpL(suite) ->
+ [];
+ttest_ssockt_csocko_medium_tcpL(doc) ->
+ [];
+ttest_ssockt_csocko_medium_tcpL(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csocko_medium_tcpL,
+ Runtime,
+ local,
+ sock, true,
+ sock, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssockt_csocko_large_tcp4(suite) ->
+ [];
+ttest_ssockt_csocko_large_tcp4(doc) ->
+ [];
+ttest_ssockt_csocko_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csocko_large_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ sock, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssockt_csocko_large_tcp6(suite) ->
+ [];
+ttest_ssockt_csocko_large_tcp6(doc) ->
+ [];
+ttest_ssockt_csocko_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csocko_large_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ sock, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: large (=3)
+%% Domain: local
+%%
+
+ttest_ssockt_csocko_large_tcpL(suite) ->
+ [];
+ttest_ssockt_csocko_large_tcpL(doc) ->
+ [];
+ttest_ssockt_csocko_large_tcpL(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csocko_large_tcpL,
+ Runtime,
+ local,
+ sock, true,
+ sock, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssockt_csockt_small_tcp4(suite) ->
+ [];
+ttest_ssockt_csockt_small_tcp4(doc) ->
+ [];
+ttest_ssockt_csockt_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csockt_small_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ sock, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssockt_csockt_small_tcp6(suite) ->
+ [];
+ttest_ssockt_csockt_small_tcp6(doc) ->
+ [];
+ttest_ssockt_csockt_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csocko_small_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ sock, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: small (=1)
+%% Domain: local
+%%
+
+ttest_ssockt_csockt_small_tcpL(suite) ->
+ [];
+ttest_ssockt_csockt_small_tcpL(doc) ->
+ [];
+ttest_ssockt_csockt_small_tcpL(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csocko_small_tcpL,
+ Runtime,
+ local,
+ sock, true,
+ sock, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssockt_csockt_medium_tcp4(suite) ->
+ [];
+ttest_ssockt_csockt_medium_tcp4(doc) ->
+ [];
+ttest_ssockt_csockt_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csockt_medium_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ sock, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssockt_csockt_medium_tcp6(suite) ->
+ [];
+ttest_ssockt_csockt_medium_tcp6(doc) ->
+ [];
+ttest_ssockt_csockt_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csockt_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ sock, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: medium (=2)
+%% Domain: local
+%%
+
+ttest_ssockt_csockt_medium_tcpL(suite) ->
+ [];
+ttest_ssockt_csockt_medium_tcpL(doc) ->
+ [];
+ttest_ssockt_csockt_medium_tcpL(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csockt_medium_tcpL,
+ Runtime,
+ local,
+ sock, true,
+ sock, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssockt_csockt_large_tcp4(suite) ->
+ [];
+ttest_ssockt_csockt_large_tcp4(doc) ->
+ [];
+ttest_ssockt_csockt_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csockt_large_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ sock, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssockt_csockt_large_tcp6(suite) ->
+ [];
+ttest_ssockt_csockt_large_tcp6(doc) ->
+ [];
+ttest_ssockt_csockt_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csockt_large_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ sock, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: large (=3)
+%% Domain: local
+%%
+
+ttest_ssockt_csockt_large_tcpL(suite) ->
+ [];
+ttest_ssockt_csockt_large_tcpL(doc) ->
+ [];
+ttest_ssockt_csockt_large_tcpL(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csockt_large_tcpL,
+ Runtime,
+ local,
+ sock, true,
+ sock, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+which_ttest_runtime(Config) when is_list(Config) ->
+ case lists:keysearch(esock_test_ttest_runtime, 1, Config) of
+ {value, {esock_test_ttest_runtime, Runtime}} ->
+ Runtime;
+ false ->
+ which_ttest_runtime_env()
+ end.
+
+which_ttest_runtime_env() ->
+ which_ttest_runtime_env(os:getenv("ESOCK_TEST_TTEST_RUNTIME")).
+
+which_ttest_runtime_env(TStr) when is_list(TStr) ->
+ which_ttest_runtime_env2(lists:reverse(TStr));
+which_ttest_runtime_env(false) ->
+ ?TTEST_RUNTIME.
+
+
+%% The format is: <int>[unit]
+%% where the optional unit can be:
+%% ms: milliseconds
+%% s: seconds (default)
+%% m: minutes
+which_ttest_runtime_env2([$s, $m | MS]) when (length(MS) > 0) ->
+ convert_time(MS, fun(X) -> X end);
+which_ttest_runtime_env2([$m | M]) when (length(M) > 0) ->
+ convert_time(M, fun(X) -> ?MINS(X) end);
+which_ttest_runtime_env2([$s | S]) when (length(S) > 0) ->
+ convert_time(S, fun(X) -> ?SECS(X) end);
+which_ttest_runtime_env2(S) ->
+ convert_time(S, fun(X) -> ?SECS(X) end).
+
+convert_time(TStrRev, Convert) ->
+ try list_to_integer(lists:reverse(TStrRev)) of
+ I -> Convert(I)
+ catch
+ _:_ ->
+ ?TTEST_RUNTIME
+ end.
+
+%% ttest_tcp(TC,
+%% Domain,
+%% ServerMod, ServerActive,
+%% ClientMod, ClientActive,
+%% MsgID, MaxOutstanding) ->
+%% ttest_tcp(TC,
+%% ?TTEST_RUNTIME,
+%% Domain,
+%% ServerMod, ServerActive,
+%% ClientMod, ClientActive,
+%% MsgID, MaxOutstanding).
+ttest_tcp(TC,
+ Runtime,
+ Domain,
+ ServerMod, ServerActive,
+ ClientMod, ClientActive,
+ MsgID, MaxOutstanding) ->
+ tc_try(TC,
+ fun() ->
+ if
+
+ (Domain =:= local) ->
+ %% On darwin we seem to hit the system limit(s)
+ %% much earlier.
+ %% The tests "mostly" work, but random cases fail
+ %% (even on reasonably powerfull machines),
+ %% so its much simpler to just skip on darwin...
+ has_support_unix_domain_socket(),
+ is_not_darwin();
+ (Domain =:= inet6) ->
+ has_support_ipv6();
+ true -> ok
+ end
+ end,
+ fun() ->
+ %% This may be overkill, depending on the runtime,
+ %% but better safe then sorry...
+ ?TT(Runtime + ?SECS(60)),
+ InitState = #{domain => Domain,
+ msg_id => MsgID,
+ max_outstanding => MaxOutstanding,
+ runtime => Runtime,
+ server_mod => ServerMod,
+ server_active => ServerActive,
+ client_mod => ClientMod,
+ client_active => ClientActive},
+ ok = ttest_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ttest_tcp(InitState) ->
+ ServerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+
+ %% *** Init part ***
+ #{desc => "create node",
+ cmd => fun(#{host := Host} = State) ->
+ case start_node(Host, server) of
+ {ok, Node} ->
+ {ok, State#{node => Node}};
+ {error, Reason} ->
+ {skip, Reason}
+ end
+ end},
+ #{desc => "monitor server node",
+ cmd => fun(#{node := Node} = _State) ->
+ true = erlang:monitor_node(Node, true),
+ ok
+ end},
+ #{desc => "start ttest (remote) server",
+ cmd => fun(#{domain := local = Domain,
+ mod := Mod,
+ active := Active,
+ node := Node} = State) ->
+ case ttest_tcp_server_start(Node,
+ Domain, Mod, Active) of
+ {ok, {{Pid, _}, Path}} ->
+ {ok, State#{rserver => Pid,
+ path => Path}};
+ {error, _} = ERROR ->
+ ERROR
+ end;
+ (#{domain := Domain,
+ mod := Mod,
+ active := Active,
+ node := Node} = State) ->
+ case ttest_tcp_server_start(Node,
+ Domain, Mod, Active) of
+ {ok, {{Pid, _}, {Addr, Port}}} ->
+ {ok, State#{rserver => Pid,
+ addr => Addr,
+ port => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{domain := local,
+ tester := Tester,
+ path := Path}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Path),
+ ok;
+ (#{tester := Tester,
+ addr := Addr,
+ port := Port}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, {Addr, Port}),
+ ok
+ end},
+
+
+ %% *** Termination ***
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester,
+ rserver := RServer} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester,
+ [{rserver, RServer}]) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ %% The remote server is in a accept, with a timeout of 5 seconds,
+ %% so may have to wait a bit...
+ #{desc => "order (remote) ttest server terminate",
+ cmd => fun(#{node := _Node,
+ rserver := RServer}) ->
+ ttest_tcp_server_stop(RServer),
+ ok
+ end},
+ #{desc => "await ttest (remote) server termination",
+ cmd => fun(#{rserver := RServer} = State) ->
+ ?SEV_AWAIT_TERMINATION(RServer),
+ State1 = maps:remove(rserver, State),
+ {ok, State1}
+ end},
+ #{desc => "stop (server) node",
+ cmd => fun(#{node := Node} = _State) ->
+ stop_node(Node)
+ end},
+ #{desc => "await (server) node termination",
+ cmd => fun(#{node := Node} = State) ->
+ receive
+ {nodedown, Node} ->
+ {ok, maps:remove(node, State)}
+ end
+ end},
+
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ ClientSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(#{domain := local} = State) ->
+ {Tester, ServerPath} =
+ ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ server_path => ServerPath}};
+ (State) ->
+ {Tester, {ServerAddr, ServerPort}} =
+ ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ server_addr => ServerAddr,
+ server_port => ServerPort}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+
+ %% *** Init part ***
+ #{desc => "create node",
+ cmd => fun(#{host := Host} = State) ->
+ case start_node(Host, client) of
+ {ok, Node} ->
+ {ok, State#{node => Node}};
+ {error, Reason} ->
+ {skip, Reason}
+ end
+ end},
+ #{desc => "monitor client node",
+ cmd => fun(#{node := Node} = _State) ->
+ true = erlang:monitor_node(Node, true),
+ ok
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+
+ %% The actual test
+ #{desc => "await continue (ttest)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, ttest),
+ ok
+ end},
+ #{desc => "start ttest (remote) client",
+ cmd => fun(#{domain := local = Domain,
+ node := Node,
+ mod := Mod,
+ active := Active,
+ msg_id := MsgID,
+ max_outstanding := MaxOutstanding,
+ runtime := RunTime,
+ server_path := Path} = State) ->
+ Self = self(),
+ Notify =
+ fun(Result) ->
+ ?SEV_ANNOUNCE_READY(Self, ttest, Result)
+ end,
+ case ttest_tcp_client_start(Node, Notify,
+ Domain, Mod,
+ Path,
+ Active,
+ MsgID, MaxOutstanding,
+ RunTime) of
+ {ok, {Pid, _MRef}} ->
+ {ok, State#{rclient => Pid}};
+ {error, _} = ERROR ->
+ ERROR
+ end;
+ (#{domain := Domain,
+ node := Node,
+ mod := Mod,
+ active := Active,
+ msg_id := MsgID,
+ max_outstanding := MaxOutstanding,
+ runtime := RunTime,
+ server_addr := Addr,
+ server_port := Port} = State) ->
+ Self = self(),
+ Notify =
+ fun(Result) ->
+ ?SEV_ANNOUNCE_READY(Self, ttest, Result)
+ end,
+ case ttest_tcp_client_start(Node, Notify,
+ Domain, Mod,
+ {Addr, Port},
+ Active,
+ MsgID, MaxOutstanding,
+ RunTime) of
+ {ok, {Pid, _MRef}} ->
+ {ok, State#{rclient => Pid}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "await ttest ready",
+ cmd => fun(#{tester := Tester,
+ rclient := RClient} = State) ->
+ case ?SEV_AWAIT_READY(RClient, rclient, ttest,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ {ok, State#{result => Result}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "await ttest (remote) client termination",
+ cmd => fun(#{rclient := RClient} = State) ->
+ ?SEV_AWAIT_TERMINATION(RClient),
+ State1 = maps:remove(rclient, State),
+ {ok, State1}
+ end},
+ #{desc => "announce ready (ttest)",
+ cmd => fun(#{tester := Tester,
+ result := Result} = State) ->
+ ?SEV_ANNOUNCE_READY(Tester, ttest, Result),
+ {ok, maps:remove(result, State)}
+ end},
+
+
+ %% *** Termination ***
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "stop (client) node",
+ cmd => fun(#{node := Node} = _State) ->
+ stop_node(Node)
+ end},
+ #{desc => "await (client) node termination",
+ cmd => fun(#{node := Node} = State) ->
+ receive
+ {nodedown, Node} ->
+ {ok, maps:remove(node, State)}
+ end
+ end},
+
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{domain := local,
+ server := Pid} = State) ->
+ {ok, Path} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{server_path => Path}};
+ (#{server := Pid} = State) ->
+ {ok, {Addr, Port}} =
+ ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{server_addr => Addr,
+ server_port => Port}}
+ end},
+
+
+ %% Start the client
+ #{desc => "order client start",
+ cmd => fun(#{domain := local,
+ client := Pid,
+ server_path := Path} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Path),
+ ok;
+ (#{client := Pid,
+ server_addr := Addr,
+ server_port := Port} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, {Addr, Port}),
+ ok
+ end},
+ #{desc => "await client ready (init)",
+ cmd => fun(#{client := Client} = _State) ->
+ ok = ?SEV_AWAIT_READY(Client, client, init)
+ end},
+
+ %% The actual test
+ #{desc => "order client continue (ttest)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, ttest),
+ ok
+ end},
+ #{desc => "await client ready (ttest)",
+ cmd => fun(#{server := Server,
+ client := Client} = State) ->
+ case ?SEV_AWAIT_READY(Client, client, ttest,
+ [{server, Server}]) of
+ {ok, Result} ->
+ {ok, State#{result => Result}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ %% *** Terminate server ***
+ #{desc => "order client terminate",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await client down",
+ cmd => fun(#{client := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(client, State),
+ {ok, State1}
+ end},
+ #{desc => "order server terminate",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Server),
+ ok
+ end},
+ #{desc => "await server down",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_TERMINATION(Server),
+ ok
+ end},
+
+
+ %% Present the results
+ #{desc => "present the results",
+ cmd => fun(#{result := Result,
+ domain := Domain,
+ server_mod := ServerTrans,
+ server_active := ServerActive,
+ client_mod := ClientTrans,
+ client_active := ClientActive,
+ msg_id := MsgID} = State) ->
+ case Result of
+ #{status := ok,
+ runtime := RunTime,
+ cnt := Cnt,
+ bcnt := BCnt} ->
+ ttest_report(Domain,
+ ServerTrans, ServerActive,
+ ClientTrans, ClientActive,
+ MsgID,
+ RunTime, BCnt, Cnt),
+ ?SEV_IPRINT(
+ "TTest results: "
+ "~n Run Time: ~s"
+ "~n Byte Count: ~s"
+ "~n Number of message exchanges: ~s"
+ "~n~n",
+ [
+ ?TTEST_LIB:format_time(RunTime),
+ if ((BCnt =:= 0) orelse (RunTime =:= 0)) ->
+ ?TTEST_LIB:format("~w, ~w",
+ [BCnt, RunTime]);
+ true ->
+ ?TTEST_LIB:format("~p => ~p byte / ms",
+ [BCnt, BCnt div RunTime])
+ end,
+ if (RunTime =:= 0) ->
+ "-";
+ true ->
+ ?TTEST_LIB:format("~p => ~p iterations / ms",
+ [Cnt, Cnt div RunTime])
+ end
+ ]),
+ {ok, maps:remove(result, State)};
+
+ #{status := Failure,
+ runtime := RunTime,
+ sid := SID,
+ rid := RID,
+ scnt := SCnt,
+ rcnt := RCnt,
+ bcnt := BCnt,
+ num := Num} ->
+ ?SEV_EPRINT("Time Test failed: "
+ "~n ~p"
+ "~n"
+ "~nwhen"
+ "~n"
+ "~n Run Time: ~s"
+ "~n Send ID: ~p"
+ "~n Recv ID: ~p"
+ "~n Send Count: ~p"
+ "~n Recv Count: ~p"
+ "~n Byte Count: ~p"
+ "~n Num Iterations: ~p",
+ [Failure,
+ ?TTEST_LIB:format_time(RunTime),
+ SID, RID, SCnt, RCnt, BCnt, Num]),
+ {error, Failure}
+ end
+ end},
+
+ %% This is just so that the printout above shall have time to come
+ %% out before then end of the test case.
+ ?SEV_SLEEP(?SECS(1)),
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ Domain = maps:get(domain, InitState),
+ LHost = local_host(),
+ LAddr = which_local_addr(Domain),
+
+ i("start server evaluator"),
+ ServerInitState = #{host => LHost,
+ addr => LAddr,
+ domain => Domain,
+ mod => maps:get(server_mod, InitState),
+ active => maps:get(server_active, InitState)},
+ Server = ?SEV_START("server", ServerSeq, ServerInitState),
+
+ i("start client evaluator"),
+ ClientInitState = #{host => LHost,
+ addr => LAddr,
+ domain => Domain,
+ mod => maps:get(client_mod, InitState),
+ active => maps:get(client_active, InitState),
+ msg_id => maps:get(msg_id, InitState),
+ max_outstanding => maps:get(max_outstanding, InitState),
+ runtime => maps:get(runtime, InitState)},
+ Client = ?SEV_START("client", ClientSeq, ClientInitState),
+
+ i("start 'tester' evaluator"),
+ TesterInitState = #{domain => Domain,
+ msg_id => maps:get(msg_id, InitState),
+ client => Client#ev.pid,
+ client_mod => maps:get(client_mod, InitState),
+ client_active => maps:get(client_active, InitState),
+ server => Server#ev.pid,
+ server_mod => maps:get(server_mod, InitState),
+ server_active => maps:get(server_active, InitState)},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator(s)"),
+ ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]).
+
+
+
+ttest_tcp_server_start(Node, Domain, gen, Active) ->
+ TransportMod = socket_test_ttest_tcp_gen,
+ Transport = {TransportMod, #{domain => Domain}},
+ socket_test_ttest_tcp_server:start_monitor(Node, Transport, Active);
+ttest_tcp_server_start(Node, Domain, sock, Active) ->
+ TransportMod = socket_test_ttest_tcp_socket,
+ Transport = {TransportMod, #{domain => Domain,
+ async => true,
+ method => plain}},
+ socket_test_ttest_tcp_server:start_monitor(Node, Transport, Active).
+
+ttest_tcp_server_stop(Pid) ->
+ socket_test_ttest_tcp_server:stop(Pid).
+
+ttest_tcp_client_start(Node,
+ Notify,
+ Domain, gen,
+ ServerInfo, Active, MsgID, MaxOutstanding, RunTime) ->
+ TransportMod = socket_test_ttest_tcp_gen,
+ Transport = {TransportMod, #{domain => Domain}},
+ socket_test_ttest_tcp_client:start_monitor(Node,
+ Notify,
+ Transport,
+ ServerInfo,
+ Active,
+ MsgID, MaxOutstanding, RunTime);
+ttest_tcp_client_start(Node,
+ Notify,
+ Domain, sock,
+ ServerInfo, Active, MsgID, MaxOutstanding, RunTime) ->
+ TransportMod = socket_test_ttest_tcp_socket,
+ Transport = {TransportMod, #{domain => Domain,
+ async => true,
+ method => plain}},
+ socket_test_ttest_tcp_client:start_monitor(Node,
+ Notify,
+ Transport,
+ ServerInfo,
+ Active,
+ MsgID, MaxOutstanding, RunTime).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-define(TTEST_MANAGER, esock_ttest_manager).
+
+-record(ttest_report_id,
+ {domain :: socket:domain(),
+ serv_trans :: gen | sock,
+ serv_active :: once | boolean(),
+ client_trans :: gen | sock,
+ client_active :: once | boolean(),
+ msg_id :: small | medium | large}).
+
+-record(ttest_report, {id :: #ttest_report_id{},
+ time :: non_neg_integer(),
+ bytes :: non_neg_integer(),
+ msgs :: non_neg_integer()}).
+
+-spec ttest_report(Domain :: socket:domain(),
+ ServTrans :: gen | sock, ServActive :: once | boolean(),
+ ClientTrans :: gen | sock, ClientActive :: once | boolean(),
+ MsgID :: 1 | 2 | 3,
+ RunTime :: non_neg_integer(),
+ NumBytes :: non_neg_integer(),
+ NumMsgs :: non_neg_integer()) -> ok.
+
+ttest_report(Domain,
+ ServTrans, ServActive,
+ ClientTrans, ClientActive,
+ MsgID,
+ RunTime,
+ NumBytes,
+ NumMsgs) ->
+ ID = #ttest_report_id{domain = Domain,
+ serv_trans = ServTrans,
+ serv_active = ServActive,
+ client_trans = ClientTrans,
+ client_active = ClientActive,
+ msg_id = ttest_msg_id_num_to_name(MsgID)},
+ Report = #ttest_report{id = ID,
+ time = RunTime,
+ bytes = NumBytes,
+ msgs = NumMsgs},
+ %% If we run just one test case, the group init has never been run
+ %% and therefor the ttest manager is not running (we also don't actually
+ %% care about collecting reports in that case).
+ (catch global:send(?TTEST_MANAGER, Report)),
+ ok.
+
+ttest_msg_id_num_to_name(1) ->
+ small;
+ttest_msg_id_num_to_name(2) ->
+ medium;
+ttest_msg_id_num_to_name(3) ->
+ large.
+
+ttest_manager_start() ->
+ Self = self(),
+ {Pid, MRef} = spawn_monitor(fun() -> ttest_manager_init(Self) end),
+ receive
+ {ttest_manager_started, Pid} ->
+ erlang:demonitor(MRef, [flush]),
+ ok;
+ {'DOWN', MRef, process, Pid, Reason} ->
+ exit({failed_starting, ttest_manager, Reason})
+ after 5000 ->
+ exit(Pid, kill),
+ exit({failed_starting, ttest_manager, timeout})
+ end.
+
+ttest_manager_stop() ->
+ case global:whereis_name(?TTEST_MANAGER) of
+ Pid when is_pid(Pid) ->
+ erlang:monitor(process, Pid),
+ global:send(?TTEST_MANAGER, stop),
+ receive
+ {'DOWN', _MRef, process, Pid, _} ->
+ ok
+ after 10000 ->
+ exit(Pid, kill),
+ ok
+ end;
+ _ ->
+ ok
+ end.
+
+ttest_manager_init(Parent) ->
+ yes = global:register_name(?TTEST_MANAGER, self()),
+ ets:new(?TTEST_MANAGER,
+ [{keypos, #ttest_report.id}, named_table, protected, ordered_set]),
+ Parent ! {ttest_manager_started, self()},
+ ttest_manager_loop().
+
+ttest_manager_loop() ->
+ receive
+ stop ->
+ ?LOGGER:format("manager stopping~n", []),
+ ttest_manager_done();
+
+ #ttest_report{id = _ID,
+ time = _RunTime,
+ bytes = _NumBytes,
+ msgs = _NumMsgs} = Report ->
+ true = ets:insert_new(?TTEST_MANAGER, Report),
+ ttest_manager_loop()
+ end.
+
+%% We are supposed to pretty print the result here...
+ttest_manager_done() ->
+ format_reports(inet),
+ %% format_reports(inet6),
+ ets:delete(?TTEST_MANAGER),
+ exit(normal).
+
+format_reports(Domain) ->
+ ?LOGGER:format("Domain ~w reports:~n~n", [Domain]),
+ format_reports(Domain, small),
+ format_reports(Domain, medium),
+ format_reports(Domain, large).
+
+format_reports(Domain, MsgID) when is_atom(MsgID) ->
+ case which_ttest_reports(Domain, MsgID) of
+ [] ->
+ ?LOGGER:format(" No ~w reports~n~n", [MsgID]);
+ Reports ->
+ ?LOGGER:format(" ~w reports: ~n", [MsgID]),
+ lists:foreach(fun(R) -> format_report(R) end, Reports)
+ end.
+
+%% This should really be a table like this:
+%%
+%% client
+%% server gen(false) gen(once) gen(true) sock(false) sock(once) sock(true)
+%% gen(false) nnn
+%% gen(once) nnn
+%% gen(true) nnn
+%% sock(false) nnn
+%% sock(once) nnn
+%% sock(true) nnn
+%%
+format_report(#ttest_report{id = #ttest_report_id{serv_trans = STrans,
+ serv_active = SActive,
+ client_trans = CTrans,
+ client_active = CActive},
+ time = RunTime,
+ bytes = BCnt,
+ msgs = MCnt}) ->
+ ?LOGGER:format(" server ~w[~w] - client ~w[~w] => "
+ "~n Run Time: ~s"
+ "~n Bytes: ~s"
+ "~n Messages: ~s"
+ "~n", [STrans, SActive, CTrans, CActive,
+ ?TTEST_LIB:format_time(RunTime),
+ if ((BCnt =:= 0) orelse (RunTime =:= 0)) ->
+ ?TTEST_LIB:format("~w, ~w",
+ [BCnt, RunTime]);
+ true ->
+ ?TTEST_LIB:format("~p => ~p byte / ms",
+ [BCnt, BCnt div RunTime])
+ end,
+ if (RunTime =:= 0) ->
+ "-";
+ true ->
+ ?TTEST_LIB:format("~p => ~p iterations / ms",
+ [MCnt, MCnt div RunTime])
+ end]),
+ ok.
+
+
+which_ttest_reports(Domain, all) ->
+ [R || R = #ttest_report{id = #ttest_report_id{domain = D}} <-
+ ets:tab2list(?TTEST_MANAGER), Domain =:= D];
+which_ttest_reports(Domain, MsgID) ->
+ [R || R = #ttest_report{id = #ttest_report_id{domain = D, msg_id = MID}} <-
+ ets:tab2list(?TTEST_MANAGER), (Domain =:= D) andalso (MsgID =:= MID)].
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% %%
+%% TICKETS %%
+%% %%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Create several acceptor processes (processes that calls socket:accept/1)
+%% and then a couple of clients connects to them without any problems.
+%% TCP, IPv4.
+otp16359_maccept_tcp4(suite) ->
+ [];
+otp16359_maccept_tcp4(doc) ->
+ [];
+otp16359_maccept_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(otp16359_maccept_tcp4,
+ fun() ->
+ InitState = #{domain => inet,
+ protocol => tcp},
+ ok = otp16359_maccept_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Create several acceptor processes (processes that calls socket:accept/1)
+%% and then a couple of clients connects to them without any problems.
+%% TCP, IPv6.
+otp16359_maccept_tcp6(suite) ->
+ [];
+otp16359_maccept_tcp6(doc) ->
+ [];
+otp16359_maccept_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(otp16359_maccept_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet6,
+ protocol => tcp},
+ ok = otp16359_maccept_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Create several acceptor processes (processes that calls socket:accept/1)
+%% and then a couple of clients connects to them without any problems.
+%% TCP, UNix Domain Sockets.
+otp16359_maccept_tcpL(suite) ->
+ [];
+otp16359_maccept_tcpL(doc) ->
+ [];
+otp16359_maccept_tcpL(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(otp16359_maccept_tcpL,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ InitState = #{domain => local,
+ protocol => default},
+ ok = otp16359_maccept_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+otp16359_maccept_tcp(InitState) ->
+ PrimAcceptorSeq =
+ [
+ %% *** Init part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ ?SEV_IPRINT("LSA: ~p", [LSA]),
+ {ok, State#{lsa => LSA}}
+ end},
+ #{desc => "create (listen) socket",
+ cmd => fun(#{domain := Domain,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, stream, Proto) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{domain := local,
+ lsock := LSock,
+ lsa := LSA} = _State) ->
+ case socket:bind(LSock, LSA) of
+ {ok, _} ->
+ ok; % We do not care about the port for local
+ {error, _} = ERROR ->
+ ERROR
+ end;
+ (#{lsock := LSock, lsa := LSA} = State) ->
+ case sock_bind(LSock, LSA) of
+ {ok, Port} ->
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{domain := local,
+ tester := Tester,
+ lsock := LSock,
+ lsa := #{path := Path}}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, {LSock, Path}),
+ ok;
+ (#{lsock := LSock, lport := LPort, tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, {LSock, LPort}),
+ ok
+ end},
+
+ %% *** The actual test ***
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "attempt to accept (with success)",
+ cmd => fun(#{lsock := LSock} = State) ->
+ case socket:accept(LSock) of
+ {ok, Sock} ->
+ ?SEV_IPRINT("Expected (accept) success: "
+ "~n ~p", [Sock]),
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept),
+ ok
+ end},
+
+ %% *** Terminate ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_TERMINATE(Tester, tester),
+ ok
+ end},
+ %% *** Close (connect) socket ***
+ #{desc => "close (connect) socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ sock_close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+ %% *** Close (listen) socket ***
+ #{desc => "close (listen) socket",
+ cmd => fun(#{domain := local,
+ lsock := Sock,
+ lsa := #{path := Path}} = State) ->
+ ok = socket:close(Sock),
+ State1 =
+ unlink_path(Path,
+ fun() ->
+ maps:remove(lsa, State)
+ end,
+ fun() -> State end),
+ {ok, maps:remove(lsock, State1)};
+ (#{lsock := LSock} = State) ->
+ sock_close(LSock),
+ {ok, maps:remove(lsock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ SecAcceptorSeq =
+ [
+ %% *** Init part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ {Tester, LSock} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ lsock => LSock}}
+
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% *** The actual test part ***
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "attempt to accept",
+ cmd => fun(#{lsock := LSock} = State) ->
+ case socket:accept(LSock) of
+ {ok, Sock} ->
+ ?SEV_IPRINT("Expected (accept) success: "
+ "~n ~p", [Sock]),
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (accept)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept),
+ ok
+ end},
+
+ %% *** Terminate ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ %% *** Close (connect) socket ***
+ #{desc => "close (connect) socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ sock_close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ ClientSeq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(#{domain := local} = State) ->
+ {Tester, Path} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, server_path => Path}};
+ (State) ->
+ {Tester, Port} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, server_port => Port}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** The init part ***
+ #{desc => "which server (local) address",
+ cmd => fun(#{domain := local = Domain,
+ server_path := Path} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ SSA = #{family => Domain, path => Path},
+ {ok, State#{lsa => LSA, server_sa => SSA}};
+ (#{domain := Domain, server_port := Port} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ SSA = LSA#{port => Port},
+ {ok, State#{lsa => LSA, server_sa => SSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, stream, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{sock := Sock, lsa := LSA} = _State) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% *** The actual test ***
+ #{desc => "await continue (connect)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, connect)
+ end},
+ #{desc => "connect to server",
+ cmd => fun(#{sock := Sock, server_sa := SSA}) ->
+ case socket:connect(Sock, SSA) of
+ ok ->
+ ?SEV_IPRINT("connected", []),
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("failed connect: "
+ "~n ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (connect)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, connect),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{domain := local,
+ sock := Sock,
+ lsa := #{path := Path}} = State) ->
+ ok = socket:close(Sock),
+ State1 =
+ unlink_path(Path,
+ fun() ->
+ maps:remove(lsa, State)
+ end,
+ fun() -> State end),
+ {ok, maps:remove(sock, State1)};
+ (#{sock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% Init part
+ #{desc => "monitor prim-acceptor",
+ cmd => fun(#{prim_acceptor := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor sec-acceptor 1",
+ cmd => fun(#{sec_acceptor1 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor sec-acceptor 2",
+ cmd => fun(#{sec_acceptor2 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client 1",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client 2",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client 3",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+
+ %% Start the prim-acceptor
+ #{desc => "start prim-acceptor",
+ cmd => fun(#{prim_acceptor := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await prim-acceptor ready (init)",
+ cmd => fun(#{prim_acceptor := Pid} = State) ->
+ {ok, {Sock, Port}} =
+ ?SEV_AWAIT_READY(Pid, prim_acceptor, init),
+ {ok, State#{lsock => Sock, lport => Port}}
+ end},
+
+ %% Start sec-acceptor-1
+ #{desc => "start sec-acceptor 1",
+ cmd => fun(#{sec_acceptor1 := Pid, lsock := LSock} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, LSock),
+ ok
+ end},
+ #{desc => "await sec-acceptor 1 ready (init)",
+ cmd => fun(#{sec_acceptor1 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, sec_acceptor1, init)
+ end},
+
+ %% Start sec-acceptor-2
+ #{desc => "start sec-acceptor 2",
+ cmd => fun(#{sec_acceptor2 := Pid, lsock := LSock} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, LSock),
+ ok
+ end},
+ #{desc => "await sec-acceptor 2 ready (init)",
+ cmd => fun(#{sec_acceptor2 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, sec_acceptor2, init)
+ end},
+
+ %% Start client-1
+ #{desc => "start client 1",
+ cmd => fun(#{client1 := Pid, lport := LPort} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, LPort),
+ ok
+ end},
+ #{desc => "await client 1 ready (init)",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, client1, init)
+ end},
+
+ %% Start client-2
+ #{desc => "start client 2",
+ cmd => fun(#{client2 := Pid, lport := LPort} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, LPort),
+ ok
+ end},
+ #{desc => "await client 2 ready (init)",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, client2, init)
+ end},
+
+ %% Start client-3
+ #{desc => "start client 3",
+ cmd => fun(#{client3 := Pid, lport := LPort} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, LPort),
+ ok
+ end},
+ #{desc => "await client 3 ready (init)",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, client3, init)
+ end},
+
+ %% Activate the acceptor(s)
+ #{desc => "active prim-acceptor",
+ cmd => fun(#{prim_acceptor := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, accept),
+ ok
+ end},
+ #{desc => "active sec-acceptor 1",
+ cmd => fun(#{sec_acceptor1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, accept),
+ ok
+ end},
+ #{desc => "active sec-acceptor 2",
+ cmd => fun(#{sec_acceptor2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, accept),
+ ok
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ %% Activate the client(s)
+ #{desc => "active client 1",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, connect),
+ ok
+ end},
+
+ ?SEV_SLEEP(100),
+
+ #{desc => "active client 2",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, connect),
+ ok
+ end},
+
+ ?SEV_SLEEP(100),
+
+ #{desc => "active client 3",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, connect),
+ ok
+ end},
+
+ %% Await acceptor(s) completions
+ #{desc => "await prim-acceptor ready (accept)",
+ cmd => fun(#{prim_acceptor := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, prim_acceptor, accept)
+ end},
+ #{desc => "await sec-acceptor 1 ready (accept)",
+ cmd => fun(#{sec_acceptor1 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, sec_acceptor1, accept)
+ end},
+ #{desc => "await sec-acceptor 2 ready (accept)",
+ cmd => fun(#{sec_acceptor2 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, sec_acceptor2, accept)
+ end},
+ #{desc => "await client 1 ready (connect)",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, client1, connect)
+ end},
+ #{desc => "await client 2 ready (connect)",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, client2, connect)
+ end},
+ #{desc => "await client 3 ready (connect)",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, client3, connect)
+ end},
+
+ %% Terminate
+ #{desc => "order client 1 to terminate",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await client 1 termination",
+ cmd => fun(#{client1 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(client1, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order client 2 to terminate",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await client 2 termination",
+ cmd => fun(#{client2 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(client2, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order client 3 to terminate",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await client 3 termination",
+ cmd => fun(#{client3 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(client3, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order prim-acceptor to terminate",
+ cmd => fun(#{prim_acceptor := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await prim-acceptor termination",
+ cmd => fun(#{prim_acceptor := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(prim_acceptor, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order sec-acceptor 1 to terminate",
+ cmd => fun(#{sec_acceptor1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await sec-acceptor 1 termination",
+ cmd => fun(#{sec_acceptor1 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(sec_acceptor1, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order sec-acceptor 2 to terminate",
+ cmd => fun(#{sec_acceptor2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await sec-acceptor 2 termination",
+ cmd => fun(#{sec_acceptor2 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(sec_acceptor2, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+
+ i("create prim-acceptor evaluator"),
+ PrimAInitState = InitState,
+ PrimAcceptor = ?SEV_START("prim-acceptor", PrimAcceptorSeq, PrimAInitState),
+
+ i("create sec-acceptor 1 evaluator"),
+ SecAInitState1 = maps:remove(domain, InitState),
+ SecAcceptor1 = ?SEV_START("sec-acceptor-1", SecAcceptorSeq, SecAInitState1),
+
+ i("create sec-acceptor 2 evaluator"),
+ SecAInitState2 = SecAInitState1,
+ SecAcceptor2 = ?SEV_START("sec-acceptor-2", SecAcceptorSeq, SecAInitState2),
+
+ i("create client 1 evaluator"),
+ ClientInitState1 = InitState,
+ Client1 = ?SEV_START("client-1", ClientSeq, ClientInitState1),
+
+ i("create client 2 evaluator"),
+ ClientInitState2 = InitState,
+ Client2 = ?SEV_START("client-2", ClientSeq, ClientInitState2),
+
+ i("create client 3 evaluator"),
+ ClientInitState3 = InitState,
+ Client3 = ?SEV_START("client-3", ClientSeq, ClientInitState3),
+
+ i("create tester evaluator"),
+ TesterInitState = #{prim_acceptor => PrimAcceptor#ev.pid,
+ sec_acceptor1 => SecAcceptor1#ev.pid,
+ sec_acceptor2 => SecAcceptor2#ev.pid,
+ client1 => Client1#ev.pid,
+ client2 => Client2#ev.pid,
+ client3 => Client3#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator(s)"),
+ ok = ?SEV_AWAIT_FINISH([PrimAcceptor, SecAcceptor1, SecAcceptor2,
+ Client1, Client2, Client3,
+ Tester]).
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This mechanism has only one purpose: So that we are able to kill
+%% the node-starter process if it takes to long. The node-starter
+%% runs on the local node.
+%% This crapola is hopefully temporary, but we have seen that on
+%% some platforms the ct_slave:start simply hangs.
+-define(NODE_START_TIMEOUT, 10000).
+start_node(Host, NodeName) ->
+ start_node(Host, NodeName, ?NODE_START_TIMEOUT).
+
+start_node(Host, NodeName, Timeout) ->
+ {NodeStarter, _} =
+ spawn_monitor(fun() -> exit(start_unique_node(Host, NodeName)) end),
+ receive
+ {'DOWN', _, process, NodeStarter, Result} ->
+ %% i("Node Starter (~p) reported: ~p", [NodeStarter, Result]),
+ Result
+ after Timeout ->
+ exit(NodeStarter, kill),
+ {error, {failed_starting_node, NodeName, timeout}}
+ end.
+
+start_unique_node(Host, NodeName) ->
+ UniqueNodeName = f("~w_~w", [NodeName, erlang:system_time(millisecond)]),
+ case do_start_node(Host, UniqueNodeName) of
+ {ok, _} = OK ->
+ global:sync(),
+ %% i("Node ~p started: "
+ %% "~n Nodes: ~p"
+ %% "~n Logger: ~p"
+ %% "~n Global Names: ~p",
+ %% [NodeName, nodes(),
+ %% global:whereis_name(socket_test_logger),
+ %% global:registered_names()]),
+ OK;
+ {error, Reason, _} ->
+ {error, Reason}
+ end.
+
+do_start_node(Host, NodeName) when is_list(NodeName) ->
+ do_start_node(Host, list_to_atom(NodeName));
+do_start_node(Host, NodeName) when is_atom(NodeName) ->
+ Dir = filename:dirname(code:which(?MODULE)),
+ Flags = "-pa " ++ Dir,
+ Opts = [{monitor_master, true}, {erl_flags, Flags}],
+ ct_slave:start(Host, NodeName, Opts).
+
+
+stop_node(Node) ->
+ case ct_slave:stop(Node) of
+ {ok, _} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+sock_open(Domain, Type, Proto) ->
+ try socket:open(Domain, Type, Proto) of
+ {ok, Socket} ->
+ Socket;
+ {error, Reason} ->
+ ?FAIL({open, Reason})
+ catch
+ C:E:S ->
+ ?FAIL({open, C, E, S})
+ end.
+
+
+sock_bind(Sock, LSA) ->
+ try socket:bind(Sock, LSA) of
+ {ok, _} = OK ->
+ OK;
+ {error, eaddrnotavail = Reason} ->
+ ?SEV_IPRINT("Address not available"),
+ throw({skip, Reason});
+ {error, _} = ERROR ->
+ ERROR
+ catch
+ C:E:S ->
+ ?FAIL({bind, C, E, S})
+ end.
+
+sock_connect(Sock, SockAddr) ->
+ try socket:connect(Sock, SockAddr) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ ?FAIL({connect, Reason})
+ catch
+ C:E:S ->
+ ?FAIL({connect, C, E, S})
+ end.
+
+sock_sockname(Sock) ->
+ try socket:sockname(Sock) of
+ {ok, SockAddr} ->
+ SockAddr;
+ {error, Reason} ->
+ ?FAIL({sockname, Reason})
+ catch
+ C:E:S ->
+ ?FAIL({sockname, C, E, S})
+ end.
+
+sock_close(Sock) ->
+ try socket:close(Sock) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ i("sock_close -> error: ~p", [Reason]),
+ ?FAIL({close, Reason})
+ catch
+ C:E:S ->
+ i("sock_close -> failed: ~p, ~p, ~p", [C, E, S]),
+ ?FAIL({close, C, E, S})
+ end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+local_host() ->
+ try net_adm:localhost() of
+ Host when is_list(Host) ->
+ %% Convert to shortname if long
+ case string:tokens(Host, [$.]) of
+ [H|_] ->
+ list_to_atom(H)
+ end
+ catch
+ C:E:S ->
+ erlang:raise(C, E, S)
+ end.
+
+
+%% The point of this is to "ensure" that paths from different test runs
+%% don't clash.
+mk_unique_path() ->
+ [NodeName | _] = string:tokens(atom_to_list(node()), [$@]),
+ Path = ?LIB:f("/tmp/esock_~s_~w", [NodeName, erlang:system_time(nanosecond)]),
+ ensure_unique_path(Path).
+
+ensure_unique_path(Path) ->
+ case file:read_file_info(Path) of
+ {ok, _} -> % Ouch, append a unique ID and try again
+ ensure_unique_path(Path, 1);
+ {error, _} ->
+ %% We assume this means it does not exist yet...
+ %% If we have several process in paralell trying to create
+ %% (unique) path's, then we are in trouble. To *really* be
+ %% on the safe side we should have a (central) path registry...
+ Path
+ end.
+
+ensure_unique_path(Path, ID) when (ID < 100) -> % If this is not enough...
+ NewPath = ?LIB:f("~s_~w", [Path, ID]),
+ case file:read_file_info(NewPath) of
+ {ok, _} -> % Ouch, this also existed, increment and try again
+ ensure_unique_path(Path, ID + 1);
+ {error, _} -> % We assume this means it does not exist yet...
+ NewPath
+ end;
+ensure_unique_path(_, _) ->
+ skip("Could not create unique path").
+
+
+which_local_socket_addr(local = Domain) ->
+ #{family => Domain,
+ path => mk_unique_path()};
+
+%% This gets the local socket address (not 127.0...)
+%% We should really implement this using the (new) net module,
+%% but until that gets the necessary functionality...
+which_local_socket_addr(Domain) ->
+ case ?LIB:which_local_host_info(Domain) of
+ {ok, #{addr := Addr}} ->
+ #{family => Domain,
+ addr => Addr};
+ {error, Reason} ->
+ ?FAIL(Reason)
+ end.
+
+
+
+which_local_addr(local = _Domain) ->
+ mk_unique_path();
+
+%% This gets the local address (not 127.0...)
+%% We should really implement this using the (new) net module,
+%% but until that gets the necessary functionality...
+which_local_addr(Domain) ->
+ ?LIB:which_local_addr(Domain).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Here are all the *general* test case condition functions.
+
+%% We also need to (be able to) figure out the multicast address,
+%% which we only support for some platforms (linux and sunos).
+%% We don't do that here, but since we can only do that (find a
+%% multicast address) for specific platforms, we check that we are
+%% on of those platforms here.
+has_support_ip_multicast() ->
+ case os:type() of
+ {unix, OsName} when (OsName =:= linux) orelse
+ (OsName =:= sunos) ->
+ case ?LIB:which_local_host_info(inet) of
+ {ok, #{flags := Flags}} ->
+ case lists:member(multicast, Flags) of
+ true ->
+ ok;
+ false ->
+ not_supported(multicast)
+ end;
+ {error, Reason} ->
+ not_supported({multicast, Reason})
+ end;
+ {unix, OsName} ->
+ skip(?F("Not Supported: platform ~w", [OsName]));
+ Type ->
+ skip(?F("Not Supported: platform ~p", [Type]))
+ end.
+
+
+%% --- SOCK socket option test functions ---
+
+has_support_sock_acceptconn() ->
+ has_support_socket_option_sock(acceptconn).
+
+has_support_sock_bindtodevice() ->
+ has_support_socket_option_sock(bindtodevice).
+
+has_support_sock_broadcast() ->
+ has_support_socket_option_sock(broadcast),
+ case ?LIB:which_local_host_info(inet) of
+ {ok, #{flags := Flags}} ->
+ case lists:member(broadcast, Flags) of
+ true ->
+ ok;
+ false ->
+ not_supported({broadcast, Flags})
+ end;
+ {error, Reason} ->
+ not_supported({broadcast, Reason})
+ end.
+
+has_support_sock_debug() ->
+ has_support_socket_option_sock(debug).
+
+has_support_sock_domain() ->
+ has_support_socket_option_sock(domain).
+
+has_support_sock_dontroute() ->
+ has_support_socket_option_sock(dontroute).
+
+has_support_sock_keepalive() ->
+ has_support_socket_option_sock(keepalive).
+
+has_support_sock_oobinline() ->
+ has_support_socket_option_sock(oobinline).
+
+has_support_sock_passcred() ->
+ has_support_socket_option_sock(passcred).
+
+has_support_sock_peek_off() ->
+ has_support_socket_option_sock(peek_off).
+
+has_support_sock_peercred() ->
+ has_support_socket_option_sock(peercred).
+
+has_support_sock_priority() ->
+ has_support_socket_option_sock(priority).
+
+has_support_sock_rcvbuf() ->
+ has_support_socket_option_sock(rcvbuf).
+
+has_support_sock_rcvlowat() ->
+ has_support_socket_option_sock(rcvlowat).
+
+has_support_sock_rcvtimeo() ->
+ has_support_socket_option_sock(rcvtimeo).
+
+has_support_sock_sndbuf() ->
+ has_support_socket_option_sock(sndbuf).
+
+has_support_sock_sndlowat() ->
+ has_support_socket_option_sock(sndlowat).
+
+has_support_sock_sndtimeo() ->
+ has_support_socket_option_sock(sndtimeo).
+
+has_support_sock_timestamp() ->
+ has_support_socket_option_sock(timestamp).
+
+
+%% --- IP socket option test functions ---
+
+has_support_ip_add_membership() ->
+ has_support_socket_option_ip(add_membership).
+
+has_support_ip_drop_membership() ->
+ has_support_socket_option_ip(drop_membership).
+
+has_support_ip_pktinfo() ->
+ has_support_socket_option_ip(pktinfo).
+
+has_support_ip_recvopts() ->
+ has_support_socket_option_ip(recvopts).
+
+has_support_ip_recvorigdstaddr() ->
+ has_support_socket_option_ip(recvorigdstaddr).
+
+has_support_ip_recvtos() ->
+ has_support_socket_option_ip(recvtos).
+
+has_support_ip_recvtos_and_or_sock_timestamp() ->
+ case (socket:is_supported(options, ip, recvtos) orelse
+ socket:is_supported(options, socket, timestamp)) of
+ true ->
+ ok;
+ false ->
+ skip(?F("Neither needed opts "
+ "ip:recvtos or socket:timestamp supported", []))
+ end.
+
+has_support_ip_recvttl() ->
+ has_support_socket_option_ip(recvttl).
+
+has_support_ip_tos() ->
+ has_support_socket_option_ip(tos).
+
+has_support_ip_recverr() ->
+ has_support_socket_option_ip(recverr).
+
+
+%% --- IPv6 socket option test functions ---
+
+has_support_ipv6_flowinfo() ->
+ has_support_socket_option_ipv6(flowinfo).
+
+has_support_ipv6_hoplimit_or_recvhoplimit() ->
+ %% case (socket:is_supported(options, ipv6, recvhoplimit) orelse
+ %% socket:is_supported(options, ipv6, hoplimit)) of
+ case is_any_options_supported([{ipv6, recvhoplimit}, {ipv6, hoplimit}]) of
+ true ->
+ ok;
+ false ->
+ skip(?F("Neither recvhoplimit or hoplimit supported", []))
+ end.
+
+has_support_ipv6_recvpktinfo() ->
+ has_support_socket_option_ipv6(recvpktinfo).
+
+
+has_support_ipv6_tclass_or_recvtclass() ->
+ case is_any_options_supported([{ipv6, recvtclass}, {ipv6, tclass}]) of
+ true ->
+ ok;
+ false ->
+ skip(?F("Neither recvtclass or tclass supported", []))
+ end.
+
+
+has_support_ipv6_recverr() ->
+ has_support_socket_option_ipv6(recverr).
+
+
+%% --- TCP socket option test functions ---
+
+has_support_tcp_congestion() ->
+ has_support_socket_option_tcp(congestion).
+
+has_support_tcp_cork() ->
+ has_support_socket_option_tcp(cork).
+
+has_support_tcp_maxseg() ->
+ has_support_socket_option_tcp(maxseg).
+
+has_support_tcp_nodelay() ->
+ has_support_socket_option_tcp(nodelay).
+
+
+%% --- UDP socket option test functions ---
+
+has_support_udp_cork() ->
+ has_support_socket_option_udp(cork).
+
+
+%% --- General purpose socket option test functions ---
+
+has_support_socket_option_sock(Opt) ->
+ has_support_socket_option(socket, Opt).
+
+has_support_socket_option_ip(Opt) ->
+ has_support_socket_option(ip, Opt).
+
+has_support_socket_option_ipv6(Opt) ->
+ has_support_socket_option(ipv6, Opt).
+
+has_support_socket_option_tcp(Opt) ->
+ has_support_socket_option(tcp, Opt).
+
+has_support_socket_option_udp(Opt) ->
+ has_support_socket_option(udp, Opt).
+
+
+has_support_socket_option(Level, Option) ->
+ case socket:is_supported(options, Level, Option) of
+ true ->
+ ok;
+ false ->
+ skip(?F("Not Supported: ~w option ~w", [Level, Option]))
+ end.
+
+is_any_options_supported(Options) ->
+ Pred = fun({Level, Option}) -> socket:is_supported(options, Level, Option) end,
+ lists:any(Pred, Options).
+
+
+%% --- Send flag test functions ---
+
+has_support_send_flag_oob() ->
+ has_support_send_flag(oob).
+
+has_support_send_flag(Flag) ->
+ has_support_send_or_recv_flag("Send", send_flags, Flag).
+
+has_support_recv_flag_oob() ->
+ has_support_recv_flag(oob).
+
+has_support_recv_flag_peek() ->
+ has_support_recv_flag(peek).
+
+has_support_recv_flag(Flag) ->
+ has_support_send_or_recv_flag("Recv", recv_flags, Flag).
+
+has_support_send_or_recv_flag(Pre, Key, Flag) ->
+ case socket:is_supported(Key, Flag) of
+ true ->
+ ok;
+ false ->
+ skip(?F("~s Flag ~w *Not* Supported", [Pre, Flag]))
+ end.
+
+
+%% Checks that the version is "good enough" (of the specified platform).
+
+is_good_enough_linux(CondVsn) ->
+ is_good_enough_platform(unix, linux, CondVsn).
+
+is_good_enough_darwin(CondVsn) ->
+ is_good_enough_platform(unix, darwin, CondVsn).
+
+is_good_enough_platform(Family, Name, CondVsn) ->
+ case os:type() of
+ {Family, Name} ->
+ ID = fun() -> ?F("~w:~w", [Family, Name]) end,
+ is_good_enough_platform2(os:version(), CondVsn, ID);
+ _ ->
+ ok
+ end.
+
+is_good_enough_platform2(Vsn, CondVsn, _) when (Vsn > CondVsn) ->
+ ok;
+is_good_enough_platform2(Vsn, CondVsn, ID) ->
+ skip(?F("Not 'good enough' ~s (~p <= ~p)", [ID(), Vsn, CondVsn])).
+
+is_not_freebsd() ->
+ is_not_platform(freebsd, "FreeBSD").
+
+is_not_openbsd() ->
+ is_not_platform(openbsd, "OpenBSD").
+
+is_not_netbsd() ->
+ is_not_platform(netbsd, "NetBSD").
+
+is_not_darwin() ->
+ is_not_platform(darwin, "Darwin").
+
+is_not_platform(Platform, PlatformStr)
+ when is_atom(Platform) andalso is_list(PlatformStr) ->
+ case os:type() of
+ {unix, Platform} ->
+ skip("This does not work on " ++ PlatformStr);
+ _ ->
+ ok
+ end.
+
+
+unix_domain_socket_host_cond() ->
+ unix_domain_socket_host_cond(os:type(), os:version()).
+
+unix_domain_socket_host_cond({unix, linux}, {M, _, _}) when (M < 3) ->
+ skip("TC may not work on this version");
+unix_domain_socket_host_cond(_, _) ->
+ ok.
+
+has_support_unix_domain_socket() ->
+ case os:type() of
+ {win32, _} ->
+ skip("Not supported");
+ _ ->
+ case socket:is_supported(local) of
+ true ->
+ ok;
+ false ->
+ skip("Not supported")
+ end
+ end.
+
+has_support_sctp() ->
+ case os:type() of
+ {win32, _} ->
+ skip("Not supported");
+ _ ->
+ case socket:is_supported(sctp) of
+ true ->
+ ok;
+ false ->
+ skip("Not supported")
+ end
+ end.
+
+
+%% The idea is that this function shall test if the test host has
+%% support for IPv6. If not, there is no point in running IPv6 tests.
+%% Currently we just skip.
+has_support_ipv6() ->
+ ?LIB:has_support_ipv6().
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+unlink_path(Path) ->
+ unlink_path(Path, fun() -> ok end, fun() -> ok end).
+
+unlink_path(Path, Success, Failure) when is_list(Path) andalso
+ is_function(Success, 0) andalso
+ is_function(Failure, 0) ->
+ ?SEV_IPRINT("try unlink path: "
+ "~n ~s", [Path]),
+ case os:cmd("unlink " ++ Path) of
+ "" ->
+ ?SEV_IPRINT("path unlinked: "
+ "~n Path: ~s", [Path]),
+ Success();
+ Result ->
+ ?SEV_EPRINT("unlink maybe failed: "
+ "~n Path: ~s"
+ "~n Res: ~s", [Path, Result]),
+ Failure()
+ end;
+unlink_path(_, _, _) ->
+ ok.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+not_supported(What) ->
+ skip({not_supported, What}).
+
+not_yet_implemented() ->
+ skip("not yet implemented").
+
+skip(Reason) ->
+ throw({skip, Reason}).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+lookup(Key, Default, Config) ->
+ case lists:keysearch(Key, 1, Config) of
+ {value, {Key, Value}} ->
+ Value;
+ _ ->
+ Default
+ end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+t() ->
+ ts(ms).
+
+ts(ms) ->
+ erlang:monotonic_time(milli_seconds).
+
+tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) ->
+ T1 = A1*1000000000+B1*1000+(C1 div 1000),
+ T2 = A2*1000000000+B2*1000+(C2 div 1000),
+ T2 - T1.
+
+
+formated_timestamp() ->
+ format_timestamp(os:timestamp()).
+
+format_timestamp({_N1, _N2, _N3} = TS) ->
+ {_Date, Time} = calendar:now_to_local_time(TS),
+ %% {YYYY,MM,DD} = Date,
+ {Hour,Min,Sec} = Time,
+ %% FormatTS =
+ %% io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w.~w",
+ %% [YYYY, MM, DD, Hour, Min, Sec, N3]),
+ FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w", [Hour, Min, Sec]),
+ lists:flatten(FormatTS).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+set_tc_name(N) when is_atom(N) ->
+ set_tc_name(atom_to_list(N));
+set_tc_name(N) when is_list(N) ->
+ put(tc_name, N).
+
+%% get_tc_name() ->
+%% get(tc_name).
+
+tc_begin(TC) ->
+ OldVal = process_flag(trap_exit, true),
+ put(old_trap_exit, OldVal),
+ set_tc_name(TC),
+ tc_print("begin ***",
+ "~n----------------------------------------------------~n", "").
+
+tc_end(Result) when is_list(Result) ->
+ OldVal = erase(old_trap_exit),
+ process_flag(trap_exit, OldVal),
+ tc_print("done: ~s", [Result],
+ "", "----------------------------------------------------~n~n"),
+ ok.
+
+%% *** tc_try/2,3 ***
+%% Case: Basically the test case name
+%% TCCondFun: A fun that is evaluated before the actual test case
+%% The point of this is that it can performs checks to
+%% see if we shall run the test case at all.
+%% For instance, the test case may only work in specific
+%% conditions.
+%% FCFun: The test case fun
+tc_try(Case, TCFun) ->
+ TCCondFun = fun() -> ok end,
+ tc_try(Case, TCCondFun, TCFun).
+
+tc_try(Case, TCCondFun, TCFun)
+ when is_atom(Case) andalso
+ is_function(TCCondFun, 0) andalso
+ is_function(TCFun, 0) ->
+ tc_begin(Case),
+ try TCCondFun() of
+ ok ->
+ try
+ begin
+ TCFun(),
+ ?SLEEP(?SECS(1)),
+ tc_end("ok")
+ end
+ catch
+ C:{skip, _} = SKIP when ((C =:= throw) orelse (C =:= exit)) ->
+ %% i("catched[tc] (skip): "
+ %% "~n C: ~p"
+ %% "~n SKIP: ~p"
+ %% "~n", [C, SKIP]),
+ tc_end( f("skipping(catched,~w,tc)", [C]) ),
+ SKIP;
+ C:E:S ->
+ %% i("catched[tc]: "
+ %% "~n C: ~p"
+ %% "~n E: ~p"
+ %% "~n S: ~p"
+ %% "~n", [C, E, S]),
+ tc_end( f("failed(catched,~w,tc)", [C]) ),
+ erlang:raise(C, E, S)
+ end;
+ {skip, _} = SKIP ->
+ tc_end("skipping(tc)"),
+ SKIP;
+ {error, Reason} ->
+ tc_end("failed(tc)"),
+ exit({tc_cond_failed, Reason})
+ catch
+ C:{skip, _} = SKIP when ((C =:= throw) orelse (C =:= exit)) ->
+ %% i("catched[cond] (skip): "
+ %% "~n C: ~p"
+ %% "~n SKIP: ~p"
+ %% "~n", [C, SKIP]),
+ tc_end( f("skipping(catched,~w,cond)", [C]) ),
+ SKIP;
+ C:E:S ->
+ %% i("catched[cond]: "
+ %% "~n C: ~p"
+ %% "~n E: ~p"
+ %% "~n S: ~p"
+ %% "~n", [C, E, S]),
+ tc_end( f("failed(catched,~w,cond)", [C]) ),
+ erlang:raise(C, E, S)
+ end.
+
+
+tc_print(F, Before, After) ->
+ tc_print(F, [], Before, After).
+
+tc_print(F, A, Before, After) ->
+ Name = tc_which_name(),
+ FStr = f("*** [~s][~s][~p] " ++ F ++ "~n",
+ [formated_timestamp(),Name,self()|A]),
+ io:format(user, Before ++ FStr ++ After, []).
+
+tc_which_name() ->
+ case get(tc_name) of
+ undefined ->
+ case get(sname) of
+ undefined ->
+ "";
+ SName when is_list(SName) ->
+ SName
+ end;
+ Name when is_list(Name) ->
+ Name
+ end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This function prints various host info, which might be usefull
+%% when analyzing the test suite (results).
+%% It also returns a "factor" that can be used when deciding
+%% the load for some test cases (traffic). Such as run time or
+%% number of iterations. This only works for some OSes (linux).
+%% Also, mostly it just returns the factor 1.
+%% At this time we just look at BogoMIPS!
+analyze_and_print_host_info() ->
+ {OsFam, OsName} = os:type(),
+ Version =
+ case os:version() of
+ {Maj, Min, Rel} ->
+ f("~w.~w.~w", [Maj, Min, Rel]);
+ VStr ->
+ VStr
+ end,
+ case {OsFam, OsName} of
+ {unix, linux} ->
+ analyze_and_print_linux_host_info(Version);
+ {unix, openbsd} ->
+ analyze_and_print_openbsd_host_info(Version);
+ {unix, freebsd} ->
+ analyze_and_print_freebsd_host_info(Version);
+ {unix, netbsd} ->
+ analyze_and_print_netbsd_host_info(Version);
+ {unix, sunos} ->
+ analyze_and_print_solaris_host_info(Version);
+ {win32, nt} ->
+ analyze_and_print_win_host_info(Version);
+ _ ->
+ io:format("OS Family: ~p"
+ "~n OS Type: ~p"
+ "~n Version: ~p"
+ "~n Num Schedulers: ~s"
+ "~n", [OsFam, OsName, Version, str_num_schedulers()]),
+ num_schedulers_to_factor()
+ end.
+
+str_num_schedulers() ->
+ try erlang:system_info(schedulers) of
+ N -> f("~w", [N])
+ catch
+ _:_:_ -> "-"
+ end.
+
+num_schedulers_to_factor() ->
+ try erlang:system_info(schedulers) of
+ 1 ->
+ 10;
+ 2 ->
+ 5;
+ N when (N =< 6) ->
+ 2;
+ _ ->
+ 1
+ catch
+ _:_:_ ->
+ 10
+ end.
+
+
+
+%% --- Linux ---
+
+linux_which_distro(Version) ->
+ case file:read_file_info("/etc/issue") of
+ {ok, _} ->
+ case [string:trim(S) ||
+ S <- string:tokens(os:cmd("cat /etc/issue"), [$\n])] of
+ [DistroStr|_] ->
+ io:format("Linux: ~s"
+ "~n ~s"
+ "~n",
+ [Version, DistroStr]),
+ case DistroStr of
+ "Wind River Linux" ++ _ ->
+ wind_river;
+ "MontaVista" ++ _ ->
+ montavista;
+ "Yellow Dog" ++ _ ->
+ yellow_dog;
+ _ ->
+ other
+ end;
+ X ->
+ io:format("Linux: ~s"
+ "~n ~p"
+ "~n",
+ [Version, X]),
+ other
+ end;
+ _ ->
+ io:format("Linux: ~s"
+ "~n", [Version]),
+ other
+ end.
+
+
+analyze_and_print_linux_host_info(Version) ->
+ Distro = linux_which_distro(Version),
+ Factor =
+ case (catch linux_which_cpuinfo(Distro)) of
+ {ok, {CPU, BogoMIPS}} ->
+ io:format("CPU: "
+ "~n Model: ~s"
+ "~n BogoMIPS: ~w"
+ "~n Num Schedulers: ~s"
+ "~n", [CPU, BogoMIPS, str_num_schedulers()]),
+ if
+ (BogoMIPS > 20000) ->
+ 1;
+ (BogoMIPS > 10000) ->
+ 2;
+ (BogoMIPS > 5000) ->
+ 3;
+ (BogoMIPS > 2000) ->
+ 5;
+ (BogoMIPS > 1000) ->
+ 8;
+ true ->
+ 10
+ end;
+ {ok, CPU} ->
+ io:format("CPU: "
+ "~n Model: ~s"
+ "~n Num Schedulers: ~s"
+ "~n", [CPU, str_num_schedulers()]),
+ num_schedulers_to_factor();
+ _ ->
+ 5
+ end,
+ %% Check if we need to adjust the factor because of the memory
+ try linux_which_meminfo() of
+ AddFactor ->
+ Factor + AddFactor
+ catch
+ _:_:_ ->
+ Factor
+ end.
+
+
+linux_cpuinfo_lookup(Key) when is_list(Key) ->
+ linux_info_lookup(Key, "/proc/cpuinfo").
+
+linux_cpuinfo_cpu() ->
+ case linux_cpuinfo_lookup("cpu") of
+ [Model] ->
+ Model;
+ _ ->
+ "-"
+ end.
+
+linux_cpuinfo_motherboard() ->
+ case linux_cpuinfo_lookup("motherboard") of
+ [MB] ->
+ MB;
+ _ ->
+ "-"
+ 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] ->
+ try bogomips_to_int(TBM)
+ catch
+ _:_:_ ->
+ "-"
+ end;
+ _ ->
+ "-"
+ end.
+
+bogomips_to_int(BM) ->
+ try list_to_float(BM) of
+ F ->
+ floor(F)
+ catch
+ _:_:_ ->
+ try list_to_integer(BM) of
+ I ->
+ I
+ catch
+ _:_:_ ->
+ throw(noinfo)
+ end
+ end.
+
+linux_cpuinfo_model() ->
+ case linux_cpuinfo_lookup("model") of
+ [M] ->
+ M;
+ _ ->
+ "-"
+ end.
+
+linux_cpuinfo_platform() ->
+ case linux_cpuinfo_lookup("platform") of
+ [P] ->
+ P;
+ _ ->
+ "-"
+ end.
+
+linux_cpuinfo_model_name() ->
+ case linux_cpuinfo_lookup("model name") of
+ [P|_] ->
+ P;
+ _X ->
+ "-"
+ end.
+
+linux_cpuinfo_processor() ->
+ case linux_cpuinfo_lookup("Processor") of
+ [P] ->
+ P;
+ _ ->
+ "-"
+ end.
+
+linux_which_cpuinfo(montavista) ->
+ CPU =
+ case linux_cpuinfo_cpu() of
+ "-" ->
+ throw(noinfo);
+ Model ->
+ case linux_cpuinfo_motherboard() of
+ "-" ->
+ Model;
+ MB ->
+ Model ++ " (" ++ MB ++ ")"
+ end
+ end,
+ case linux_cpuinfo_bogomips() of
+ "-" ->
+ {ok, CPU};
+ BMips ->
+ {ok, {CPU, BMips}}
+ end;
+
+linux_which_cpuinfo(yellow_dog) ->
+ CPU =
+ case linux_cpuinfo_cpu() of
+ "-" ->
+ throw(noinfo);
+ Model ->
+ case linux_cpuinfo_motherboard() of
+ "-" ->
+ Model;
+ MB ->
+ Model ++ " (" ++ MB ++ ")"
+ end
+ end,
+ {ok, CPU};
+
+linux_which_cpuinfo(wind_river) ->
+ CPU =
+ case linux_cpuinfo_model() of
+ "-" ->
+ throw(noinfo);
+ Model ->
+ case linux_cpuinfo_platform() of
+ "-" ->
+ Model;
+ Platform ->
+ Model ++ " (" ++ Platform ++ ")"
+ end
+ end,
+ case linux_cpuinfo_total_bogomips() of
+ "-" ->
+ {ok, CPU};
+ BMips ->
+ {ok, {CPU, BMips}}
+ end;
+
+linux_which_cpuinfo(other) ->
+ %% Check for x86 (Intel or AMD)
+ CPU =
+ case linux_cpuinfo_model_name() of
+ "-" ->
+ %% ARM (at least some distros...)
+ case linux_cpuinfo_processor() of
+ "-" ->
+ %% Ok, we give up
+ throw(noinfo);
+ Proc ->
+ Proc
+ end;
+ ModelName ->
+ ModelName
+ end,
+ case linux_cpuinfo_bogomips() of
+ "-" ->
+ {ok, CPU};
+ BMips ->
+ {ok, {CPU, BMips}}
+ end.
+
+linux_meminfo_lookup(Key) when is_list(Key) ->
+ linux_info_lookup(Key, "/proc/meminfo").
+
+linux_meminfo_memtotal() ->
+ case linux_meminfo_lookup("MemTotal") of
+ [X] ->
+ X;
+ _ ->
+ "-"
+ end.
+
+%% We *add* the value this return to the Factor.
+linux_which_meminfo() ->
+ case linux_meminfo_memtotal() of
+ "-" ->
+ 0;
+ MemTotal ->
+ io:format("Memory:"
+ "~n ~s"
+ "~n", [MemTotal]),
+ case string:tokens(MemTotal, [$ ]) of
+ [MemSzStr, MemUnit] ->
+ MemSz2 = list_to_integer(MemSzStr),
+ MemSz3 =
+ case string:to_lower(MemUnit) of
+ "kb" ->
+ MemSz2;
+ "mb" ->
+ MemSz2*1024;
+ "gb" ->
+ MemSz2*1024*1024;
+ _ ->
+ throw(noinfo)
+ end,
+ if
+ (MemSz3 >= 8388608) ->
+ 0;
+ (MemSz3 >= 4194304) ->
+ 1;
+ (MemSz3 >= 2097152) ->
+ 3;
+ true ->
+ 5
+ end;
+ _X ->
+ 0
+ end
+ end.
+
+
+linux_info_lookup(Key, File) ->
+ try [string:trim(S) || S <- string:tokens(os:cmd("grep " ++ "\"" ++ Key ++ "\"" ++ " " ++ File), [$:,$\n])] of
+ Info ->
+ linux_info_lookup_collect(Key, Info, [])
+ catch
+ _:_:_ ->
+ "-"
+ end.
+
+linux_info_lookup_collect(_Key, [], Values) ->
+ lists:reverse(Values);
+linux_info_lookup_collect(Key, [Key, Value|Rest], Values) ->
+ linux_info_lookup_collect(Key, Rest, [Value|Values]);
+linux_info_lookup_collect(_, _, Values) ->
+ lists:reverse(Values).
+
+
+%% --- OpenBSD ---
+
+%% Just to be clear: This is ***not*** scientific...
+analyze_and_print_openbsd_host_info(Version) ->
+ io:format("OpenBSD:"
+ "~n Version: ~p"
+ "~n", [Version]),
+ Extract =
+ fun(Key) ->
+ string:tokens(string:trim(os:cmd("sysctl " ++ Key)), [$=])
+ end,
+ try
+ begin
+ CPU =
+ case Extract("hw.model") of
+ ["hw.model", Model] ->
+ string:trim(Model);
+ _ ->
+ "-"
+ end,
+ CPUSpeed =
+ case Extract("hw.cpuspeed") of
+ ["hw.cpuspeed", Speed] ->
+ list_to_integer(Speed);
+ _ ->
+ -1
+ end,
+ NCPU =
+ case Extract("hw.ncpufound") of
+ ["hw.ncpufound", N] ->
+ list_to_integer(N);
+ _ ->
+ -1
+ end,
+ Memory =
+ case Extract("hw.physmem") of
+ ["hw.physmem", PhysMem] ->
+ list_to_integer(PhysMem) div 1024;
+ _ ->
+ -1
+ end,
+ io:format("CPU:"
+ "~n Model: ~s"
+ "~n Speed: ~w"
+ "~n N: ~w"
+ "~nMemory:"
+ "~n ~w KB"
+ "~n", [CPU, CPUSpeed, NCPU, Memory]),
+ CPUFactor =
+ if
+ (CPUSpeed =:= -1) ->
+ 1;
+ (CPUSpeed >= 2000) ->
+ if
+ (NCPU >= 4) ->
+ 1;
+ (NCPU >= 2) ->
+ 2;
+ true ->
+ 3
+ end;
+ true ->
+ if
+ (NCPU >= 4) ->
+ 2;
+ (NCPU >= 2) ->
+ 3;
+ true ->
+ 4
+ end
+ end,
+ MemAddFactor =
+ if
+ (Memory =:= -1) ->
+ 0;
+ (Memory >= 8388608) ->
+ 0;
+ (Memory >= 4194304) ->
+ 1;
+ (Memory >= 2097152) ->
+ 2;
+ true ->
+ 3
+ end,
+ CPUFactor + MemAddFactor
+ end
+ catch
+ _:_:_ ->
+ 1
+ end.
+
+
+%% --- FreeBSD ---
+
+analyze_and_print_freebsd_host_info(Version) ->
+ io:format("FreeBSD:"
+ "~n Version: ~p"
+ "~n", [Version]),
+ %% This test require that the program 'sysctl' is in the path.
+ %% First test with 'which sysctl', if that does not work
+ %% try with 'which /sbin/sysctl'. If that does not work either,
+ %% we skip the test...
+ try
+ begin
+ SysCtl =
+ case string:trim(os:cmd("which sysctl")) of
+ [] ->
+ case string:trim(os:cmd("which /sbin/sysctl")) of
+ [] ->
+ throw(sysctl);
+ SC2 ->
+ SC2
+ end;
+ SC1 ->
+ SC1
+ end,
+ Extract =
+ fun(Key) ->
+ string:tokens(string:trim(os:cmd(SysCtl ++ " " ++ Key)),
+ [$:])
+ end,
+ CPU = analyze_freebsd_cpu(Extract),
+ CPUSpeed = analyze_freebsd_cpu_speed(Extract),
+ NCPU = analyze_freebsd_ncpu(Extract),
+ Memory = analyze_freebsd_memory(Extract),
+ io:format("CPU:"
+ "~n Model: ~s"
+ "~n Speed: ~w"
+ "~n N: ~w"
+ "~n Num Schedulers: ~w"
+ "~nMemory:"
+ "~n ~w KB"
+ "~n",
+ [CPU, CPUSpeed, NCPU,
+ erlang:system_info(schedulers), Memory]),
+ CPUFactor =
+ if
+ (CPUSpeed =:= -1) ->
+ 1;
+ (CPUSpeed >= 2000) ->
+ if
+ (NCPU >= 4) ->
+ 1;
+ (NCPU >= 2) ->
+ 2;
+ true ->
+ 3
+ end;
+ true ->
+ if
+ (NCPU =:= -1) ->
+ 1;
+ (NCPU >= 4) ->
+ 2;
+ (NCPU >= 2) ->
+ 3;
+ true ->
+ 4
+ end
+ end,
+ MemAddFactor =
+ if
+ (Memory =:= -1) ->
+ 0;
+ (Memory >= 8388608) ->
+ 0;
+ (Memory >= 4194304) ->
+ 1;
+ (Memory >= 2097152) ->
+ 2;
+ true ->
+ 3
+ end,
+ CPUFactor + MemAddFactor
+ end
+ catch
+ _:_:_ ->
+ io:format("CPU:"
+ "~n Num Schedulers: ~w"
+ "~n", [erlang:system_info(schedulers)]),
+ case erlang:system_info(schedulers) of
+ 1 ->
+ 10;
+ 2 ->
+ 5;
+ _ ->
+ 2
+ end
+ end.
+
+analyze_freebsd_cpu(Extract) ->
+ analyze_freebsd_item(Extract, "hw.model", fun(X) -> X end, "-").
+
+analyze_freebsd_cpu_speed(Extract) ->
+ analyze_freebsd_item(Extract,
+ "hw.clockrate",
+ fun(X) -> list_to_integer(X) end,
+ -1).
+
+analyze_freebsd_ncpu(Extract) ->
+ analyze_freebsd_item(Extract,
+ "hw.ncpu",
+ fun(X) -> list_to_integer(X) end,
+ -1).
+
+analyze_freebsd_memory(Extract) ->
+ analyze_freebsd_item(Extract,
+ "hw.physmem",
+ fun(X) -> list_to_integer(X) div 1024 end,
+ -1).
+
+analyze_freebsd_item(Extract, Key, Process, Default) ->
+ try
+ begin
+ case Extract(Key) of
+ [Key, Model] ->
+ Process(string:trim(Model));
+ _ ->
+ Default
+ end
+ end
+ catch
+ _:_:_ ->
+ Default
+ end.
+
+
+%% --- NetBSD ---
+
+analyze_and_print_netbsd_host_info(Version) ->
+ io:format("NetBSD:"
+ "~n Version: ~p"
+ "~n", [Version]),
+ %% This test require that the program 'sysctl' is in the path.
+ %% First test with 'which sysctl', if that does not work
+ %% try with 'which /sbin/sysctl'. If that does not work either,
+ %% we skip the test...
+ try
+ begin
+ SysCtl =
+ case string:trim(os:cmd("which sysctl")) of
+ [] ->
+ case string:trim(os:cmd("which /sbin/sysctl")) of
+ [] ->
+ throw(sysctl);
+ SC2 ->
+ SC2
+ end;
+ SC1 ->
+ SC1
+ end,
+ Extract =
+ fun(Key) ->
+ [string:trim(S) ||
+ S <-
+ string:tokens(string:trim(os:cmd(SysCtl ++ " " ++ Key)),
+ [$=])]
+ end,
+ CPU = analyze_netbsd_cpu(Extract),
+ Machine = analyze_netbsd_machine(Extract),
+ Arch = analyze_netbsd_machine_arch(Extract),
+ CPUSpeed = analyze_netbsd_cpu_speed(Extract),
+ NCPU = analyze_netbsd_ncpu(Extract),
+ Memory = analyze_netbsd_memory(Extract),
+ io:format("CPU:"
+ "~n Model: ~s (~s, ~s)"
+ "~n Speed: ~w MHz"
+ "~n N: ~w"
+ "~n Num Schedulers: ~w"
+ "~nMemory:"
+ "~n ~w KB"
+ "~n",
+ [CPU, Machine, Arch, CPUSpeed, NCPU,
+ erlang:system_info(schedulers), Memory]),
+ CPUFactor =
+ if
+ (CPUSpeed =:= -1) ->
+ 1;
+ (CPUSpeed >= 2000) ->
+ if
+ (NCPU >= 4) ->
+ 1;
+ (NCPU >= 2) ->
+ 2;
+ true ->
+ 3
+ end;
+ true ->
+ if
+ (NCPU =:= -1) ->
+ 1;
+ (NCPU >= 4) ->
+ 2;
+ (NCPU >= 2) ->
+ 3;
+ true ->
+ 4
+ end
+ end,
+ MemAddFactor =
+ if
+ (Memory =:= -1) ->
+ 0;
+ (Memory >= 8388608) ->
+ 0;
+ (Memory >= 4194304) ->
+ 1;
+ (Memory >= 2097152) ->
+ 2;
+ true ->
+ 3
+ end,
+ CPUFactor + MemAddFactor
+ end
+ catch
+ _:_:_ ->
+ io:format("CPU:"
+ "~n Num Schedulers: ~w"
+ "~n", [erlang:system_info(schedulers)]),
+ case erlang:system_info(schedulers) of
+ 1 ->
+ 10;
+ 2 ->
+ 5;
+ _ ->
+ 2
+ end
+ end.
+
+analyze_netbsd_cpu(Extract) ->
+ analyze_netbsd_item(Extract, "hw.model", fun(X) -> X end, "-").
+
+analyze_netbsd_machine(Extract) ->
+ analyze_netbsd_item(Extract, "hw.machine", fun(X) -> X end, "-").
+
+analyze_netbsd_machine_arch(Extract) ->
+ analyze_netbsd_item(Extract, "hw.machine_arch", fun(X) -> X end, "-").
+
+analyze_netbsd_cpu_speed(Extract) ->
+ analyze_netbsd_item(Extract, "machdep.dmi.processor-frequency",
+ fun(X) -> case string:tokens(X, [$\ ]) of
+ [MHz, "MHz"] ->
+ list_to_integer(MHz);
+ _ ->
+ -1
+ end
+ end, "-").
+
+analyze_netbsd_ncpu(Extract) ->
+ analyze_netbsd_item(Extract,
+ "hw.ncpu",
+ fun(X) -> list_to_integer(X) end,
+ -1).
+
+analyze_netbsd_memory(Extract) ->
+ analyze_netbsd_item(Extract,
+ "hw.physmem64",
+ fun(X) -> list_to_integer(X) div 1024 end,
+ -1).
+
+analyze_netbsd_item(Extract, Key, Process, Default) ->
+ analyze_freebsd_item(Extract, Key, Process, Default).
+
+
+
+%% --- Solaris ---
+
+analyze_and_print_solaris_host_info(Version) ->
+ Release =
+ case file:read_file_info("/etc/release") of
+ {ok, _} ->
+ case [string:trim(S) || S <- string:tokens(os:cmd("cat /etc/release"), [$\n])] of
+ [Rel | _] ->
+ Rel;
+ _ ->
+ "-"
+ end;
+ _ ->
+ "-"
+ end,
+ %% Display the firmware device tree root properties (prtconf -b)
+ Props = [list_to_tuple([string:trim(PS) || PS <- Prop]) ||
+ Prop <- [string:tokens(S, [$:]) ||
+ S <- string:tokens(os:cmd("prtconf -b"), [$\n])]],
+ BannerName = case lists:keysearch("banner-name", 1, Props) of
+ {value, {_, BN}} ->
+ string:trim(BN);
+ _ ->
+ "-"
+ end,
+ InstructionSet =
+ case string:trim(os:cmd("isainfo -k")) of
+ "Pseudo-terminal will not" ++ _ ->
+ "-";
+ IS ->
+ IS
+ end,
+ PtrConf = [list_to_tuple([string:trim(S) || S <- Items]) || Items <- [string:tokens(S, [$:]) || S <- string:tokens(os:cmd("prtconf"), [$\n])], length(Items) > 1],
+ SysConf =
+ case lists:keysearch("System Configuration", 1, PtrConf) of
+ {value, {_, SC}} ->
+ SC;
+ _ ->
+ "-"
+ end,
+ NumPhysProc =
+ begin
+ NPPStr = string:trim(os:cmd("psrinfo -p")),
+ try list_to_integer(NPPStr) of
+ _ ->
+ NPPStr
+ catch
+ _:_:_ ->
+ "-"
+ end
+ end,
+ NumProc = try integer_to_list(length(string:tokens(os:cmd("psrinfo"), [$\n]))) of
+ NPStr ->
+ NPStr
+ catch
+ _:_:_ ->
+ "-"
+ end,
+ MemSz =
+ case lists:keysearch("Memory size", 1, PtrConf) of
+ {value, {_, MS}} ->
+ MS;
+ _ ->
+ "-"
+ end,
+ io:format("Solaris: ~s"
+ "~n Release: ~s"
+ "~n Banner Name: ~s"
+ "~n Instruction Set: ~s"
+ "~n CPUs: ~s (~s)"
+ "~n System Config: ~s"
+ "~n Memory Size: ~s"
+ "~n Num Schedulers: ~s"
+ "~n~n", [Version, Release, BannerName, InstructionSet,
+ NumPhysProc, NumProc,
+ SysConf, MemSz,
+ str_num_schedulers()]),
+ MemFactor =
+ try string:tokens(MemSz, [$ ]) of
+ [SzStr, "Mega" ++ _] ->
+ try list_to_integer(SzStr) of
+ Sz when Sz > 8192 ->
+ 0;
+ Sz when Sz > 4096 ->
+ 1;
+ Sz when Sz > 2048 ->
+ 2;
+ _ ->
+ 5
+ catch
+ _:_:_ ->
+ 10
+ end;
+ [SzStr, "Giga" ++ _] ->
+ try list_to_integer(SzStr) of
+ Sz when Sz > 8 ->
+ 0;
+ Sz when Sz > 4 ->
+ 1;
+ Sz when Sz > 2 ->
+ 2;
+ _ ->
+ 5
+ catch
+ _:_:_ ->
+ 10
+ end;
+ _ ->
+ 10
+ catch
+ _:_:_ ->
+ 10
+ end,
+ try erlang:system_info(schedulers) of
+ 1 ->
+ 10;
+ 2 ->
+ 5;
+ N when (N =< 6) ->
+ 2;
+ _ ->
+ 1
+ catch
+ _:_:_ ->
+ 10
+ end + MemFactor.
+
+
+%% --- Windows ---
+
+analyze_and_print_win_host_info(Version) ->
+ SysInfo = which_win_system_info(),
+ OsName = win_sys_info_lookup(os_name, SysInfo),
+ OsVersion = win_sys_info_lookup(os_version, SysInfo),
+ SysMan = win_sys_info_lookup(system_manufacturer, SysInfo),
+ SysMod = win_sys_info_lookup(system_model, SysInfo),
+ NumProcs = win_sys_info_lookup(num_processors, SysInfo),
+ TotPhysMem = win_sys_info_lookup(total_phys_memory, SysInfo),
+ io:format("Windows: ~s"
+ "~n OS Version: ~s (~p)"
+ "~n System Manufacturer: ~s"
+ "~n System Model: ~s"
+ "~n Number of Processor(s): ~s"
+ "~n Total Physical Memory: ~s"
+ "~n Num Schedulers: ~s"
+ "~n", [OsName, OsVersion, Version,
+ SysMan, SysMod, NumProcs, TotPhysMem,
+ str_num_schedulers()]),
+ MemFactor =
+ try
+ begin
+ [MStr, MUnit|_] =
+ string:tokens(lists:delete($,, TotPhysMem), [$\ ]),
+ case string:to_lower(MUnit) of
+ "gb" ->
+ try list_to_integer(MStr) of
+ M when M > 8 ->
+ 0;
+ M when M > 4 ->
+ 1;
+ M when M > 2 ->
+ 2;
+ _ ->
+ 5
+ catch
+ _:_:_ ->
+ 10
+ end;
+ "mb" ->
+ try list_to_integer(MStr) of
+ M when M > 8192 ->
+ 0;
+ M when M > 4096 ->
+ 1;
+ M when M > 2048 ->
+ 2;
+ _ ->
+ 5
+ catch
+ _:_:_ ->
+ 10
+ end;
+ _ ->
+ 10
+ end
+ end
+ catch
+ _:_:_ ->
+ 10
+ end,
+ CPUFactor =
+ case erlang:system_info(schedulers) of
+ 1 ->
+ 10;
+ 2 ->
+ 5;
+ _ ->
+ 2
+ end,
+ CPUFactor + MemFactor.
+
+win_sys_info_lookup(Key, SysInfo) ->
+ win_sys_info_lookup(Key, SysInfo, "-").
+
+win_sys_info_lookup(Key, SysInfo, Def) ->
+ case lists:keysearch(Key, 1, SysInfo) of
+ {value, {Key, Value}} ->
+ Value;
+ false ->
+ Def
+ end.
+
+%% This function only extracts the prop(s) we actually care about!
+%% On some hosts this (systeminfo) takes a *long time* (several minutes).
+%% And since there is no way to provide a timeout to the os command call,
+%% we have to wrap it in a process.
+which_win_system_info() ->
+ F = fun() ->
+ try
+ begin
+ SysInfo = os:cmd("systeminfo"),
+ process_win_system_info(
+ string:tokens(SysInfo, [$\r, $\n]), [])
+ end
+ catch
+ C:E:S ->
+ io:format("Failed get or process System info: "
+ " Error Class: ~p"
+ " Error: ~p"
+ " Stack: ~p"
+ "~n", [C, E, S]),
+ []
+ end
+ end,
+ ?LIB:pcall(F, ?MINS(1), []).
+
+process_win_system_info([], Acc) ->
+ Acc;
+process_win_system_info([H|T], Acc) ->
+ case string:tokens(H, [$:]) of
+ [Key, Value] ->
+ case string:to_lower(Key) of
+ "os name" ->
+ process_win_system_info(T,
+ [{os_name, string:trim(Value)}|Acc]);
+ "os version" ->
+ process_win_system_info(T,
+ [{os_version, string:trim(Value)}|Acc]);
+ "system manufacturer" ->
+ process_win_system_info(T,
+ [{system_manufacturer, string:trim(Value)}|Acc]);
+ "system model" ->
+ process_win_system_info(T,
+ [{system_model, string:trim(Value)}|Acc]);
+ "processor(s)" ->
+ [NumProcStr|_] = string:tokens(Value, [$\ ]),
+ T2 = lists:nthtail(list_to_integer(NumProcStr), T),
+ process_win_system_info(T2,
+ [{num_processors, NumProcStr}|Acc]);
+ "total physical memory" ->
+ process_win_system_info(T,
+ [{total_phys_memory, string:trim(Value)}|Acc]);
+ _ ->
+ process_win_system_info(T, Acc)
+ end;
+ _ ->
+ process_win_system_info(T, Acc)
+ end.
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+l2a(S) when is_list(S) ->
+ list_to_atom(S).
+
+l2b(L) when is_list(L) ->
+ list_to_binary(L).
+
+b2l(B) when is_binary(B) ->
+ binary_to_list(B).
+
+f(F, A) ->
+ lists:flatten(io_lib:format(F, A)).
+
+i(F) ->
+ i(F, []).
+
+i(F, A) ->
+ FStr = f("[~s] " ++ F, [formated_timestamp()|A]),
+ io:format(user, FStr ++ "~n", []),
+ io:format(FStr, []).
+
diff --git a/lib/kernel/test/socket_test_evaluator.erl b/lib/kernel/test/socket_test_evaluator.erl
new file mode 100644
index 0000000000..694f0d5f1e
--- /dev/null
+++ b/lib/kernel/test/socket_test_evaluator.erl
@@ -0,0 +1,664 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(socket_test_evaluator).
+
+%% Evaluator control functions
+-export([
+ start/3,
+ await_finish/1
+ ]).
+
+%% Functions used by evaluators to interact with eachother
+-export([
+ %% Announce functions
+ %% (Send an announcement from one evaluator to another)
+ announce_start/1, announce_start/2,
+ announce_continue/2, announce_continue/3,
+ announce_ready/2, announce_ready/3,
+ announce_terminate/1,
+
+ %% Await functions
+ %% (Wait for an announcement from another evaluator)
+ await_start/0, await_start/1,
+ await_continue/3, await_continue/4,
+ await_ready/3, await_ready/4,
+ await_terminate/2, await_terminate/3,
+ await_termination/1, await_termination/2
+ ]).
+
+%% Utility functions
+-export([
+ iprint/2, % Info printouts
+ eprint/2 % Error printouts
+ ]).
+
+-export_type([
+ ev/0,
+ initial_evaluator_state/0,
+ evaluator_state/0,
+ command_fun/0,
+ command/0
+ ]).
+
+
+-include("socket_test_evaluator.hrl").
+
+-type ev() :: #ev{}.
+-type initial_evaluator_state() :: map().
+-type evaluator_state() :: term().
+-type command_fun() ::
+ fun((State :: evaluator_state()) -> ok) |
+ fun((State :: evaluator_state()) -> {ok, evaluator_state()}) |
+ fun((State :: evaluator_state()) -> {error, term()}).
+
+-type command() :: #{desc := string(),
+ cmd := command_fun()}.
+
+
+%% ============================================================================
+
+-define(LIB, socket_test_lib).
+-define(LOGGER, socket_test_logger).
+
+-define(EXTRA_NOTHING, '$nothing').
+-define(ANNOUNCEMENT_START, '$start').
+-define(ANNOUNCEMENT_READY, '$ready').
+-define(ANNOUNCEMENT_CONTINUE, '$continue').
+-define(ANNOUNCEMENT_TERMINATE, '$terminate').
+
+-define(START_NAME_NONE, '$no-name').
+-define(START_SLOGAN, ?ANNOUNCEMENT_START).
+-define(TERMINATE_SLOGAN, ?ANNOUNCEMENT_TERMINATE).
+
+
+%% ============================================================================
+
+-spec start(Name, Seq, Init) -> ev() when
+ Name :: string(),
+ Seq :: [command()],
+ Init :: initial_evaluator_state().
+
+start(Name, Seq, InitState)
+ when is_list(Name) andalso is_list(Seq) andalso (Seq =/= []) ->
+ %% Make sure 'parent' is not already used
+ case maps:find(parent, InitState) of
+ {ok, _} ->
+ erlang:error({already_used, parent});
+ error ->
+ InitState2 = InitState#{parent => self()},
+ Pid = erlang:spawn_link(
+ fun() -> init(Name, Seq, InitState2) end),
+ %% MRef = erlang:monitor(process, Pid),
+ #ev{name = Name, pid = Pid}%, mref = MRef}
+ end.
+
+init(Name, Seq, Init) ->
+ put(sname, Name),
+ process_flag(trap_exit, true),
+ loop(1, Seq, Init).
+
+loop(_ID, [], FinalState) ->
+ exit(FinalState);
+loop(ID, [#{desc := Desc,
+ cmd := Cmd}|Cmds], State) when is_function(Cmd, 1) ->
+ iprint("evaluate command ~2w: ~s", [ID, Desc]),
+ try Cmd(State) of
+ ok ->
+ loop(ID + 1, Cmds, State);
+ {ok, NewState} ->
+ loop(ID + 1, Cmds, NewState);
+ {skip, Reason} ->
+ ?SEV_IPRINT("command ~w skip: "
+ "~n ~p", [ID, Reason]),
+ exit({skip, Reason});
+ {error, Reason} ->
+ ?SEV_EPRINT("command ~w failed: "
+ "~n ~p", [ID, Reason]),
+ exit({command_failed, ID, Reason, State})
+ catch
+ C:{skip, command} = E:_ when ((C =:= throw) orelse (C =:= exit)) ->
+ %% Secondary skip
+ exit(E);
+ C:{skip, R} = E:_ when ((C =:= throw) orelse (C =:= exit)) ->
+ ?SEV_IPRINT("command ~w skip catched(~w): "
+ "~n Reason: ~p", [ID, C, R]),
+ exit(E);
+ C:E:S ->
+ ?SEV_EPRINT("command ~w crashed: "
+ "~n Class: ~p"
+ "~n Error: ~p"
+ "~n Call Stack: ~p", [ID, C, E, S]),
+ exit({command_crashed, ID, {C,E,S}, State})
+ end.
+
+
+%% ============================================================================
+
+-spec await_finish(Evs) -> term() when
+ Evs :: [ev()].
+
+await_finish(Evs) ->
+ await_finish(Evs, [], []).
+
+await_finish([], _, []) ->
+ ok;
+await_finish([], _OK, Fails) ->
+ ?SEV_EPRINT("Fails: "
+ "~n ~p", [Fails]),
+ Fails;
+await_finish(Evs, OK, Fails) ->
+ receive
+ %% Successfull termination of evaluator
+ {'DOWN', _MRef, process, Pid, normal} ->
+ {Evs2, OK2, Fails2} = await_finish_normal(Pid, Evs, OK, Fails),
+ await_finish(Evs2, OK2, Fails2);
+ {'EXIT', Pid, normal} ->
+ {Evs2, OK2, Fails2} = await_finish_normal(Pid, Evs, OK, Fails),
+ await_finish(Evs2, OK2, Fails2);
+
+ %% The evaluator can skip the test case:
+ {'DOWN', _MRef, process, Pid, {skip, Reason}} ->
+ %% ?SEV_IPRINT("await_finish -> skip (down) received: "
+ %% "~n Pid: ~p"
+ %% "~n Reason: ~p", [Pid, Reason]),
+ await_finish_skip(Pid, Reason, Evs, OK);
+ {'EXIT', Pid, {skip, Reason}} ->
+ %% ?SEV_IPRINT("await_finish -> skip (exit) received: "
+ %% "~n Pid: ~p"
+ %% "~n Reason: ~p", [Pid, Reason]),
+ await_finish_skip(Pid, Reason, Evs, OK);
+
+ %% Evaluator failed
+ {'DOWN', _MRef, process, Pid, Reason} ->
+ %% ?SEV_IPRINT("await_finish -> fail (down) received: "
+ %% "~n Pid: ~p"
+ %% "~n Reason: ~p", [Pid, Reason]),
+ {Evs2, OK2, Fails2} =
+ await_finish_fail(Pid, Reason, Evs, OK, Fails),
+ await_finish(Evs2, OK2, Fails2);
+ {'EXIT', Pid, Reason} ->
+ %% ?SEV_IPRINT("await_finish -> fail (exit) received: "
+ %% "~n Pid: ~p"
+ %% "~n Reason: ~p", [Pid, Reason]),
+ {Evs2, OK2, Fails2} =
+ await_finish_fail(Pid, Reason, Evs, OK, Fails),
+ await_finish(Evs2, OK2, Fails2)
+ end.
+
+
+await_finish_normal(Pid, Evs, OK, Fails) ->
+ case lists:keysearch(Pid, #ev.pid, Evs) of
+ {value, #ev{name = Name}} ->
+ iprint("evaluator '~s' (~p) success", [Name, Pid]),
+ NewEvs = lists:keydelete(Pid, #ev.pid, Evs),
+ {NewEvs, [Pid|OK], Fails};
+ false ->
+ case lists:member(Pid, OK) of
+ true ->
+ ok;
+ false ->
+ iprint("unknown process ~p died (normal)", [Pid]),
+ ok
+ end,
+ {Evs, OK, Fails}
+ end.
+
+await_finish_skip(Pid, Reason, Evs, OK) ->
+ Evs2 =
+ case lists:keysearch(Pid, #ev.pid, Evs) of
+ {value, #ev{name = Name}} ->
+ ?SEV_IPRINT("evaluator '~s' (~p) issued SKIP: "
+ "~n ~p", [Name, Pid, Reason]),
+ lists:keydelete(Pid, #ev.pid, Evs);
+ false ->
+ case lists:member(Pid, OK) of
+ true ->
+ ?SEV_IPRINT("already terminated (ok) process ~p skip"
+ "~n ~p", [Pid]),
+ ok;
+ false ->
+ ?SEV_IPRINT("unknown process ~p issued SKIP: "
+ "~n ~p", [Pid, Reason]),
+ iprint("unknown process ~p issued SKIP: "
+ "~n ~p", [Pid, Reason])
+ end,
+ Evs
+ end,
+ await_evs_terminated(Evs2),
+ ?LIB:skip(Reason).
+
+await_evs_terminated(Evs) ->
+ Instructions =
+ [
+ %% Just wait for the evaluators to die on their own
+ {fun() -> ?SEV_IPRINT("await (no action) evs termination") end,
+ fun(_) -> ok end},
+
+ %% Send them a skip message, causing the evaluators to
+ %% die with a skip reason.
+ {fun() -> ?SEV_IPRINT("await (send skip message) evs termination") end,
+ fun(#ev{pid = Pid}) -> Pid ! skip end},
+
+ %% And if nothing else works, try to kill the remaining evaluators
+ {fun() -> ?SEV_IPRINT("await (issue exit kill) evs termination") end,
+ fun(#ev{pid = Pid}) -> exit(Pid, kill) end}],
+
+ await_evs_terminated(Evs, Instructions).
+
+await_evs_terminated([], _) ->
+ ok;
+await_evs_terminated(Evs, []) ->
+ {error, {failed_terminated, [P||#ev{pid=P} <- Evs]}};
+await_evs_terminated(Evs, [{Inform, Command}|Instructions]) ->
+ Inform(),
+ lists:foreach(Command, Evs),
+ RemEvs = await_evs_termination(Evs),
+ await_evs_terminated(RemEvs, Instructions).
+
+await_evs_termination(Evs) ->
+ await_evs_termination(Evs, 2000).
+
+await_evs_termination([], _Timeout) ->
+ [];
+await_evs_termination(Evs, Timeout) ->
+ T = t(),
+ receive
+ {'DOWN', _MRef, process, Pid, _Reason} ->
+ %% ?SEV_IPRINT("await_evs_termination -> DOWN: "
+ %% "~n Pid: ~p"
+ %% "~n Reason: ~p", [Pid, Reason]),
+ Evs2 = lists:keydelete(Pid, #ev.pid, Evs),
+ await_evs_termination(Evs2, tdiff(T, t()));
+ {'EXIT', Pid, _Reason} ->
+ %% ?SEV_IPRINT("await_evs_termination -> EXIT: "
+ %% "~n Pid: ~p"
+ %% "~n Reason: ~p", [Pid, Reason]),
+ Evs2 = lists:keydelete(Pid, #ev.pid, Evs),
+ await_evs_termination(Evs2, tdiff(T, t()))
+
+ after Timeout ->
+ Evs
+ end.
+
+
+await_finish_fail(Pid, Reason, Evs, OK, Fails) ->
+ case lists:keysearch(Pid, #ev.pid, Evs) of
+ {value, #ev{name = Name}} ->
+ iprint("evaluator '~s' (~p) failed", [Name, Pid]),
+ NewEvs = lists:keydelete(Pid, #ev.pid, Evs),
+ {NewEvs, OK, [{Pid, Reason}|Fails]};
+ false ->
+ case lists:member(Pid, OK) of
+ true ->
+ ok;
+ false ->
+ iprint("unknown process ~p died: "
+ "~n ~p", [Pid, Reason])
+ end,
+ {Evs, OK, Fails}
+ end.
+
+
+
+%% ============================================================================
+
+-spec announce_start(To) -> ok when
+ To :: pid().
+
+announce_start(To) ->
+ announce(To, ?ANNOUNCEMENT_START, ?START_SLOGAN).
+
+-spec announce_start(To, Extra) -> ok when
+ To :: pid(),
+ Extra :: term().
+
+announce_start(To, Extra) ->
+ announce(To, ?ANNOUNCEMENT_START, ?START_SLOGAN, Extra).
+
+
+%% ============================================================================
+
+-spec announce_continue(To, Slogan) -> ok when
+ To :: pid(),
+ Slogan :: atom().
+
+announce_continue(To, Slogan) ->
+ announce_continue(To, Slogan, ?EXTRA_NOTHING).
+
+-spec announce_continue(To, Slogan, Extra) -> ok when
+ To :: pid(),
+ Slogan :: atom(),
+ Extra :: term().
+
+announce_continue(To, Slogan, Extra) ->
+ announce(To, ?ANNOUNCEMENT_CONTINUE, Slogan, Extra).
+
+
+%% ============================================================================
+
+-spec announce_ready(To, Slogan) -> ok when
+ To :: pid(),
+ Slogan :: atom().
+
+announce_ready(To, Slogan) ->
+ announce_ready(To, Slogan, ?EXTRA_NOTHING).
+
+-spec announce_ready(To, Slogan, Extra) -> ok when
+ To :: pid(),
+ Slogan :: atom(),
+ Extra :: term().
+
+announce_ready(To, Slogan, Extra) ->
+ announce(To, ?ANNOUNCEMENT_READY, Slogan, Extra).
+
+
+%% ============================================================================
+
+-spec announce_terminate(To) -> ok when
+ To :: pid().
+
+announce_terminate(To) ->
+ announce(To, ?ANNOUNCEMENT_TERMINATE, ?TERMINATE_SLOGAN).
+
+
+%% ============================================================================
+
+-spec announce(To, Announcement, Slogan) -> ok when
+ To :: pid(),
+ Announcement :: atom(),
+ Slogan :: atom().
+
+announce(To, Announcement, Slogan) ->
+ announce(To, Announcement, Slogan, ?EXTRA_NOTHING).
+
+-spec announce(To, Announcement, Slogan, Extra) -> ok when
+ To :: pid(),
+ Announcement :: atom(),
+ Slogan :: atom(),
+ Extra :: term().
+
+announce(To, Announcement, Slogan, Extra)
+ when is_pid(To) andalso
+ is_atom(Announcement) andalso
+ is_atom(Slogan) ->
+ %% iprint("announce -> entry with: "
+ %% "~n To: ~p"
+ %% "~n Announcement: ~p"
+ %% "~n Slogan: ~p"
+ %% "~n Extra: ~p",
+ %% [To, Announcement, Slogan, Extra]),
+ To ! {Announcement, self(), Slogan, Extra},
+ ok.
+
+
+
+%% ============================================================================
+
+-spec await_start() -> Pid | {Pid, Extra} when
+ Pid :: pid(),
+ Extra :: term().
+
+await_start() ->
+ await_start(any).
+
+-spec await_start(Pid) -> Pid | {Pid, Extra} when
+ Pid :: pid(),
+ Extra :: term().
+
+await_start(P) when is_pid(P) orelse (P =:= any) ->
+ case await(P, ?START_NAME_NONE, ?ANNOUNCEMENT_START, ?START_SLOGAN, []) of
+ {ok, Any} when is_pid(P) ->
+ Any;
+ {ok, Pid} when is_pid(Pid) andalso (P =:= any) ->
+ Pid;
+ {ok, {Pid, _} = OK} when is_pid(Pid) andalso (P =:= any) ->
+ OK
+ end.
+
+
+%% ============================================================================
+
+-spec await_continue(From, Name, Slogan) -> ok | {ok, Extra} | {error, Reason} when
+ From :: pid(),
+ Name :: atom(),
+ Slogan :: atom(),
+ Extra :: term(),
+ Reason :: term().
+
+await_continue(From, Name, Slogan) ->
+ await_continue(From, Name, Slogan, []).
+
+-spec await_continue(From, Name, Slogan, OtherPids) ->
+ ok | {ok, Extra} | {error, Reason} when
+ From :: pid(),
+ Name :: atom(),
+ Slogan :: atom(),
+ OtherPids :: [{pid(), atom()}],
+ Extra :: term(),
+ Reason :: term().
+
+await_continue(From, Name, Slogan, OtherPids)
+ when is_pid(From) andalso
+ is_atom(Name) andalso
+ is_atom(Slogan) andalso
+ is_list(OtherPids) ->
+ await(From, Name, ?ANNOUNCEMENT_CONTINUE, Slogan, OtherPids).
+
+
+
+%% ============================================================================
+
+-spec await_ready(From, Name, Slogan) -> ok | {ok, Extra} | {error, Reason} when
+ From :: pid(),
+ Name :: atom(),
+ Slogan :: atom(),
+ Extra :: term(),
+ Reason :: term().
+
+await_ready(From, Name, Slogan) ->
+ await_ready(From, Name, Slogan, []).
+
+-spec await_ready(From, Name, Slogan, OtherPids) ->
+ ok | {ok, Extra} | {error, Reason} when
+ From :: pid(),
+ Name :: atom(),
+ Slogan :: atom(),
+ OtherPids :: [{pid(), atom()}],
+ Extra :: term(),
+ Reason :: term().
+
+await_ready(From, Name, Slogan, OtherPids)
+ when is_pid(From) andalso
+ is_atom(Name) andalso
+ is_atom(Slogan) andalso
+ is_list(OtherPids) ->
+ await(From, Name, ?ANNOUNCEMENT_READY, Slogan, OtherPids).
+
+
+
+%% ============================================================================
+
+-spec await_terminate(Pid, Name) -> ok | {error, Reason} when
+ Pid :: pid(),
+ Name :: atom(),
+ Reason :: term().
+
+await_terminate(Pid, Name) when is_pid(Pid) andalso is_atom(Name) ->
+ await_terminate(Pid, Name, []).
+
+-spec await_terminate(Pid, Name, OtherPids) -> ok | {error, Reason} when
+ Pid :: pid(),
+ Name :: atom(),
+ OtherPids :: [{pid(), atom()}],
+ Reason :: term().
+
+await_terminate(Pid, Name, OtherPids) ->
+ await(Pid, Name, ?ANNOUNCEMENT_TERMINATE, ?TERMINATE_SLOGAN, OtherPids).
+
+
+%% ============================================================================
+
+-spec await_termination(Pid) -> ok | {error, Reason} when
+ Pid :: pid(),
+ Reason :: term().
+
+await_termination(Pid) when is_pid(Pid) ->
+ await_termination(Pid, any).
+
+-spec await_termination(Pid, ExpReason) -> ok | {error, Reason} when
+ Pid :: pid(),
+ ExpReason :: term(),
+ Reason :: term().
+
+await_termination(Pid, ExpReason) ->
+ receive
+ {'DOWN', _, process, Pid, _} when (ExpReason =:= any) ->
+ ok;
+ {'DOWN', _, process, Pid, Reason} when (ExpReason =:= Reason) ->
+ ok;
+ {'DOWN', _, process, Pid, Reason} ->
+ {error, {unexpected_reason, ExpReason, Reason}}
+ end.
+
+
+%% ============================================================================
+
+%% We expect a message (announcement) from Pid, but we also watch for DOWN from
+%% both Pid and OtherPids, in which case the test has failed!
+
+-spec await(ExpPid, Name, Announcement, Slogan, OtherPids) ->
+ ok | {ok, Extra} | {error, Reason} when
+ ExpPid :: any | pid(),
+ Name :: atom(),
+ Announcement :: atom(),
+ Slogan :: atom(),
+ OtherPids :: [{pid(), atom()}],
+ Extra :: term(),
+ Reason :: term().
+
+await(ExpPid, Name, Announcement, Slogan, OtherPids)
+ when (is_pid(ExpPid) orelse (ExpPid =:= any)) andalso
+ is_atom(Name) andalso
+ is_atom(Announcement) andalso
+ is_atom(Slogan) andalso
+ is_list(OtherPids) ->
+ receive
+ skip ->
+ %% This means that another evaluator has issued a skip,
+ %% and we have been instructed to terminate as a result.
+ ?LIB:skip(command);
+ {Announcement, Pid, Slogan, ?EXTRA_NOTHING} when (ExpPid =:= any) ->
+ {ok, Pid};
+ {Announcement, Pid, Slogan, Extra} when (ExpPid =:= any) ->
+ {ok, {Pid, Extra}};
+ {Announcement, Pid, Slogan, ?EXTRA_NOTHING} when (Pid =:= ExpPid) ->
+ ok;
+ {Announcement, Pid, Slogan, Extra} when (Pid =:= ExpPid) ->
+ {ok, Extra};
+ {'DOWN', _, process, Pid, {skip, SkipReason}} when (Pid =:= ExpPid) ->
+ iprint("Unexpected SKIP from ~w (~p): "
+ "~n ~p", [Name, Pid, SkipReason]),
+ ?LIB:skip({Name, SkipReason});
+ {'DOWN', _, process, Pid, Reason} when (Pid =:= ExpPid) ->
+ eprint("Unexpected DOWN from ~w (~p): "
+ "~n ~p", [Name, Pid, Reason]),
+ {error, {unexpected_exit, Name, Reason}};
+ {'DOWN', _, process, OtherPid, Reason} ->
+ case check_down(OtherPid, Reason, OtherPids) of
+ ok ->
+ iprint("DOWN from unknown process ~p: "
+ "~n ~p"
+ "~n when"
+ "~n OtherPids: "
+ "~n ~p", [OtherPid, Reason, OtherPids]),
+ await(ExpPid, Name, Announcement, Slogan, OtherPids);
+ {error, _} = ERROR ->
+ ERROR
+ end
+ after infinity -> % For easy debugging, just change to some valid time (5000)
+ iprint("await -> timeout for msg from ~p (~w): "
+ "~n Announcement: ~p"
+ "~n Slogan: ~p"
+ "~nwhen"
+ "~n Messages: ~p",
+ [ExpPid, Name, Announcement, Slogan, pi(messages)]),
+ await(ExpPid, Name, Announcement, Slogan, OtherPids)
+ end.
+
+pi(Item) ->
+ pi(self(), Item).
+
+pi(Pid, Item) ->
+ {Item, Info} = process_info(Pid, Item),
+ Info.
+
+check_down(Pid, DownReason, Pids) ->
+ case lists:keymember(Pid, 1, Pids) of
+ {value, {_, Name}} ->
+ eprint("Unexpected DOWN from ~w (~p): "
+ "~n ~p", [Name, Pid, DownReason]),
+ {error, {unexpected_exit, Name, DownReason}};
+ false ->
+ ok
+ end.
+
+
+%% ============================================================================
+
+f(F, A) ->
+ lists:flatten(io_lib:format(F, A)).
+
+
+iprint(F, A) ->
+ print("", F, A).
+
+eprint(F, A) ->
+ print("<ERROR> ", F, A).
+
+print(Prefix, F, A) ->
+ %% The two prints is to get the output both in the shell (for when
+ %% "personal" testing is going on) and in the logs.
+ IDStr =
+ case get(sname) of
+ undefined ->
+ %% This means its not an evaluator,
+ %% or a named process. Instead its
+ %% most likely the test case itself,
+ %% so skip the name and the pid.
+ "";
+ SName ->
+ f("[~s][~p]", [SName, self()])
+ end,
+ ?LOGGER:format("[~s]~s ~s" ++ F,
+ [?LIB:formated_timestamp(), IDStr, Prefix | A]).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+t() ->
+ os:timestamp().
+
+
+tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) ->
+ T1 = A1*1000000000+B1*1000+(C1 div 1000),
+ T2 = A2*1000000000+B2*1000+(C2 div 1000),
+ T2 - T1.
+
diff --git a/lib/kernel/test/socket_test_evaluator.hrl b/lib/kernel/test/socket_test_evaluator.hrl
new file mode 100644
index 0000000000..5be49dc022
--- /dev/null
+++ b/lib/kernel/test/socket_test_evaluator.hrl
@@ -0,0 +1,68 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2018. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-ifndef(socket_test_evaluator).
+-define(socket_test_evaluator, true).
+
+-record(ev, {name :: string(),
+ pid :: pid(),
+ mref :: reference()}).
+
+-define(SEV, socket_test_evaluator).
+
+-define(SEV_START(N, S, IS), ?SEV:start(N, S, IS)).
+-define(SEV_AWAIT_FINISH(Evs), ?SEV:await_finish(Evs)).
+
+-define(SEV_ANNOUNCE_START(To), ?SEV:announce_start(To)).
+-define(SEV_ANNOUNCE_START(To, Ex), ?SEV:announce_start(To, Ex)).
+-define(SEV_ANNOUNCE_CONTINUE(To, S), ?SEV:announce_continue(To, S)).
+-define(SEV_ANNOUNCE_CONTINUE(To, S, Ex), ?SEV:announce_continue(To, S, Ex)).
+-define(SEV_ANNOUNCE_READY(To, S), ?SEV:announce_ready(To, S)).
+-define(SEV_ANNOUNCE_READY(To, S, Ex), ?SEV:announce_ready(To, S, Ex)).
+-define(SEV_ANNOUNCE_TERMINATE(To), ?SEV:announce_terminate(To)).
+
+-define(SEV_AWAIT_START(), ?SEV:await_start()).
+-define(SEV_AWAIT_START(P), ?SEV:await_start(P)).
+-define(SEV_AWAIT_CONTINUE(F, N, S), ?SEV:await_continue(F, N, S)).
+-define(SEV_AWAIT_CONTINUE(F, N, S, Ps), ?SEV:await_continue(F, N, S, Ps)).
+-define(SEV_AWAIT_READY(F, N, S), ?SEV:await_ready(F, N, S)).
+-define(SEV_AWAIT_READY(F, N, S, Ps), ?SEV:await_ready(F, N, S, Ps)).
+-define(SEV_AWAIT_TERMINATE(F, N), ?SEV:await_terminate(F, N)).
+-define(SEV_AWAIT_TERMINATE(F, N, Ps), ?SEV:await_terminate(F, N, Ps)).
+-define(SEV_AWAIT_TERMINATION(P), ?SEV:await_termination(P)).
+-define(SEV_AWAIT_TERMINATION(P, R), ?SEV:await_termination(P, R)).
+
+-define(SEV_IPRINT(F, A), ?SEV:iprint(F, A)).
+-define(SEV_IPRINT(F), ?SEV_IPRINT(F, [])).
+-define(SEV_EPRINT(F, A), ?SEV:eprint(F, A)).
+-define(SEV_EPRINT(F), ?SEV_EPRINT(F, [])).
+
+-define(SEV_SLEEP(T), #{desc => "sleep",
+ cmd => fun(_) ->
+ ?SLEEP(T),
+ ok
+ end}).
+-define(SEV_FINISH_NORMAL, #{desc => "finish",
+ cmd => fun(_) ->
+ {ok, normal}
+ end}).
+
+-endif. % -ifdef(socket_test_evaluator).
+
diff --git a/lib/kernel/test/socket_test_lib.erl b/lib/kernel/test/socket_test_lib.erl
new file mode 100644
index 0000000000..13d4a4ba8e
--- /dev/null
+++ b/lib/kernel/test/socket_test_lib.erl
@@ -0,0 +1,303 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-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%
+%%
+
+-module(socket_test_lib).
+
+-export([
+ %% Process info
+ pi/1, pi/2, pi/3,
+
+ %% Proxy call
+ pcall/3,
+
+ %% Time stuff
+ timestamp/0,
+ tdiff/2,
+ formated_timestamp/0,
+ format_timestamp/1,
+
+ %% String and format
+ f/2,
+
+ %% Generic 'has support' test function(s)
+ has_support_ipv6/0,
+
+ which_local_host_info/1,
+ which_local_addr/1,
+
+ %% Skipping
+ not_yet_implemented/0,
+ skip/1
+ ]).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-define(FAIL(R), exit(R)).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+pi(Item) when is_atom(Item) ->
+ pi(self(), Item).
+
+pi(Pid, Item) when is_pid(Pid) andalso is_atom(Item) ->
+ {Item, Info} = process_info(Pid, Item),
+ Info;
+pi(Node, Pid) when is_pid(Pid) ->
+ rpc:call(Node, erlang, process_info, [Pid]).
+
+pi(Node, Pid, Item) when is_pid(Pid) andalso is_atom(Item) ->
+ rpc:call(Node, erlang, process_info, [Pid, Item]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+pcall(F, Timeout, Default)
+ when is_function(F, 0) andalso is_integer(Timeout) andalso (Timeout > 0) ->
+ {P, M} = erlang:spawn_monitor(fun() -> exit(F()) end),
+ receive
+ {'DOWN', M, process, P, Reply} ->
+ Reply
+ after Timeout ->
+ erlang:demonitor(M, [flush]),
+ exit(P, kill),
+ Default
+ end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+timestamp() ->
+ os:timestamp().
+
+
+tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) ->
+ T1 = A1*1000000000+B1*1000+(C1 div 1000),
+ T2 = A2*1000000000+B2*1000+(C2 div 1000),
+ T2 - T1.
+
+
+formated_timestamp() ->
+ format_timestamp(os:timestamp()).
+
+format_timestamp({_N1, _N2, _N3} = TS) ->
+ {_Date, Time} = calendar:now_to_local_time(TS),
+ %% {YYYY,MM,DD} = Date,
+ {Hour,Min,Sec} = Time,
+ %% FormatTS =
+ %% io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w.~w",
+ %% [YYYY, MM, DD, Hour, Min, Sec, N3]),
+ FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w", [Hour, Min, Sec]),
+ lists:flatten(FormatTS).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+f(F, A) ->
+ lists:flatten(io_lib:format(F, A)).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+has_support_ipv6() ->
+ case socket:is_supported(ipv6) of
+ true ->
+ ok;
+ false ->
+ skip("IPv6 Not Supported")
+ end,
+ Domain = inet6,
+ LocalAddr =
+ case which_local_addr(Domain) of
+ {ok, Addr} ->
+ Addr;
+ {error, R1} ->
+ skip(f("Local Address eval failed: ~p", [R1]))
+ end,
+ ServerSock =
+ case socket:open(Domain, dgram, udp) of
+ {ok, SS} ->
+ SS;
+ {error, R2} ->
+ skip(f("(server) socket open failed: ~p", [R2]))
+ end,
+ LocalSA = #{family => Domain, addr => LocalAddr},
+ ServerPort =
+ case socket:bind(ServerSock, LocalSA) of
+ {ok, P1} ->
+ P1;
+ {error, R3} ->
+ socket:close(ServerSock),
+ skip(f("(server) socket bind failed: ~p", [R3]))
+ end,
+ ServerSA = LocalSA#{port => ServerPort},
+ ClientSock =
+ case socket:open(Domain, dgram, udp) of
+ {ok, CS} ->
+ CS;
+ {error, R4} ->
+ skip(f("(client) socket open failed: ~p", [R4]))
+ end,
+ case socket:bind(ClientSock, LocalSA) of
+ {ok, _} ->
+ ok;
+ {error, R5} ->
+ socket:close(ServerSock),
+ socket:close(ClientSock),
+ skip(f("(client) socket bind failed: ~p", [R5]))
+ end,
+ case socket:sendto(ClientSock, <<"hejsan">>, ServerSA) of
+ ok ->
+ ok;
+ {error, R6} ->
+ socket:close(ServerSock),
+ socket:close(ClientSock),
+ skip(f("failed socket sendto test: ~p", [R6]))
+ end,
+ case socket:recvfrom(ServerSock) of
+ {ok, {_, <<"hejsan">>}} ->
+ socket:close(ServerSock),
+ socket:close(ClientSock),
+ ok;
+ {error, R7} ->
+ socket:close(ServerSock),
+ socket:close(ClientSock),
+ skip(f("failed socket recvfrom test: ~p", [R7]))
+ end.
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This gets the local address (not {127, _} or {0, ...} or {16#fe80, ...})
+%% We should really implement this using the (new) net module,
+%% but until that gets the necessary functionality...
+which_local_addr(Domain) ->
+ case which_local_host_info(Domain) of
+ {ok, #{addr := Addr}} ->
+ {ok, Addr};
+ {error, _Reason} = ERROR ->
+ ERROR
+ end.
+
+
+%% Returns the interface (name), flags and address (not 127...)
+%% of the local host.
+which_local_host_info(Domain) ->
+ case inet:getifaddrs() of
+ {ok, IFL} ->
+ which_local_host_info(Domain, IFL);
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+which_local_host_info(_Domain, []) ->
+ {error, no_address};
+which_local_host_info(Domain, [{"docker" ++ _, _}|IFL]) ->
+ which_local_host_info(Domain, IFL);
+which_local_host_info(Domain, [{"br-" ++ _, _}|IFL]) ->
+ which_local_host_info(Domain, IFL);
+which_local_host_info(Domain, [{Name, IFO}|IFL]) ->
+ case if_is_running_and_not_loopback(IFO) of
+ true ->
+ try which_local_host_info2(Domain, IFO) of
+ Info ->
+ {ok, Info#{name => Name}}
+ catch
+ throw:_:_ ->
+ which_local_host_info(Domain, IFL)
+ end;
+ false ->
+ which_local_host_info(Domain, IFL)
+ end;
+which_local_host_info(Domain, [_|IFL]) ->
+ which_local_host_info(Domain, IFL).
+
+if_is_running_and_not_loopback(If) ->
+ lists:keymember(flags, 1, If) andalso
+ begin
+ {value, {flags, Flags}} = lists:keysearch(flags, 1, If),
+ (not lists:member(loopback, Flags)) andalso
+ lists:member(running, Flags)
+ end.
+
+
+which_local_host_info2(inet = _Domain, IFO) ->
+ Addr = which_local_host_info3(addr, IFO,
+ fun({A, _, _, _}) when (A =/= 127) -> true;
+ (_) -> false
+ end),
+ NetMask = which_local_host_info3(netmask, IFO,
+ fun({_, _, _, _}) -> true;
+ (_) -> false
+ end),
+ BroadAddr = which_local_host_info3(broadaddr, IFO,
+ fun({_, _, _, _}) -> true;
+ (_) -> false
+ end),
+ Flags = which_local_host_info3(flags, IFO, fun(_) -> true end),
+ #{flags => Flags,
+ addr => Addr,
+ broadaddr => BroadAddr,
+ netmask => NetMask};
+which_local_host_info2(inet6 = _Domain, IFO) ->
+ Addr = which_local_host_info3(addr, IFO,
+ fun({A, _, _, _, _, _, _, _})
+ when (A =/= 0) andalso
+ (A =/= 16#fe80) -> true;
+ (_) -> false
+ end),
+ NetMask = which_local_host_info3(netmask, IFO,
+ fun({_, _, _, _, _, _, _, _}) -> true;
+ (_) -> false
+ end),
+ Flags = which_local_host_info3(flags, IFO, fun(_) -> true end),
+ #{flags => Flags,
+ addr => Addr,
+ netmask => NetMask}.
+
+which_local_host_info3(_Key, [], _) ->
+ throw({error, no_address});
+which_local_host_info3(Key, [{Key, Val}|IFO], Check) ->
+ case Check(Val) of
+ true ->
+ Val;
+ false ->
+ which_local_host_info3(Key, IFO, Check)
+ end;
+which_local_host_info3(Key, [_|IFO], Check) ->
+ which_local_host_info3(Key, IFO, Check).
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+not_yet_implemented() ->
+ skip("not yet implemented").
+
+skip(Reason) ->
+ throw({skip, Reason}).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/lib/kernel/test/socket_test_logger.erl b/lib/kernel/test/socket_test_logger.erl
new file mode 100644
index 0000000000..f5d4c8c7b2
--- /dev/null
+++ b/lib/kernel/test/socket_test_logger.erl
@@ -0,0 +1,118 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(socket_test_logger).
+
+-export([
+ start/0, start/1,
+ stop/0,
+ format/2
+ ]).
+
+
+-define(QUIET, true).
+-define(LIB, socket_test_lib).
+-define(LOGGER, ?MODULE).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+start() ->
+ start(?QUIET).
+
+start(Quiet) ->
+ case global:whereis_name(?LOGGER) of
+ Pid when is_pid(Pid) ->
+ ok;
+ undefined ->
+ Self = self(),
+ Pid = spawn(fun() -> init(Self, Quiet) end),
+ yes = global:register_name(?LOGGER, Pid),
+ ok
+ end.
+
+
+stop() ->
+ case global:whereis_name(?LOGGER) of
+ undefined ->
+ ok;
+ Pid when is_pid(Pid) ->
+ global:unregister_name(?LOGGER),
+ Pid ! {?LOGGER, '$logger', stop},
+ ok
+ end.
+
+
+format(F, []) ->
+ do_format(F);
+format(F, A) ->
+ do_format(?LIB:f(F, A)).
+
+do_format(Msg) ->
+ case global:whereis_name(?LOGGER) of
+ undefined ->
+ ok;
+ Pid when is_pid(Pid) ->
+ Pid ! {?MODULE, '$logger', {msg, Msg}},
+ ok
+ end.
+
+init(Parent, Quiet) ->
+ put(sname, "logger"),
+ print("[~s][logger] starting~n", [?LIB:formated_timestamp()]),
+ loop(#{parent => Parent, quiet => Quiet}).
+
+loop(#{parent := Parent,
+ quiet := Quiet} = State) ->
+ receive
+ {'EXIT', Parent, _} ->
+ print("[~s][logger] parent exit~n", [?LIB:formated_timestamp()]),
+ exit(normal);
+
+ {?MODULE, '$logger', stop} ->
+ print("[~s][logger] stopping~n", [?LIB:formated_timestamp()]),
+ exit(normal);
+
+ {?MODULE, '$logger', {msg, Msg}} ->
+ print_str(Quiet, Msg),
+ loop(State)
+ end.
+
+
+print(F, A) ->
+ print_str(false, ?LIB:f(F, A)).
+
+print_str(Quiet, Str) ->
+ try
+ begin
+ if (Quiet =/= true) -> io:format(user, Str ++ "~n", []);
+ true -> ok
+ end,
+ io:format(Str, [])
+ end
+ catch
+ _:_:_ ->
+ io:format(user,
+ "~nFailed Format message:"
+ "~n~p~n", [Str]),
+ io:format("~nFailed Format message:"
+ "~n~p~n", [Str])
+ end.
+
diff --git a/lib/kernel/test/socket_test_ttest.hrl b/lib/kernel/test/socket_test_ttest.hrl
new file mode 100644
index 0000000000..1a004a9a7a
--- /dev/null
+++ b/lib/kernel/test/socket_test_ttest.hrl
@@ -0,0 +1,32 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2018. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-ifndef(socket_test_ttest).
+-define(socket_test_ttest, true).
+
+-define(TTEST_TAG, 42).
+-define(TTEST_TYPE_REQUEST, 101).
+-define(TTEST_TYPE_REPLY, 102).
+
+-define(SECS(I), timer:seconds(I)).
+
+-define(SLEEP(T), receive after T -> ok end).
+
+-endif. % -ifdef(socket_test_ttest).
diff --git a/lib/kernel/test/socket_test_ttest_client.hrl b/lib/kernel/test/socket_test_ttest_client.hrl
new file mode 100644
index 0000000000..84e736cc34
--- /dev/null
+++ b/lib/kernel/test/socket_test_ttest_client.hrl
@@ -0,0 +1,141 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2018. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-ifndef(socket_test_ttest_client).
+-define(socket_test_ttest_client, true).
+
+-define(MSG_ID_DEFAULT, 2).
+-define(RUNTIME_DEFAULT, ?SECS(10)).
+-define(MAX_ID, 16#FFFFFFFF).
+
+-define(MSG_DATA1, <<"This is test data 0123456789 0123456789 0123456789">>).
+-define(MSG_DATA2, <<"This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789">>).
+-define(MSG_DATA3, <<"This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789">>).
+
+
+-endif. % -ifdef(socket_test_ttest_client).
diff --git a/lib/kernel/test/socket_test_ttest_lib.erl b/lib/kernel/test/socket_test_ttest_lib.erl
new file mode 100644
index 0000000000..ebce16dcfa
--- /dev/null
+++ b/lib/kernel/test/socket_test_ttest_lib.erl
@@ -0,0 +1,127 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-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%
+%%
+
+-module(socket_test_ttest_lib).
+
+-compile({no_auto_import, [error/2]}).
+
+-export([
+ t/0, tdiff/2,
+ formated_timestamp/0, format_timestamp/1,
+ format_time/1,
+
+ formated_process_stats/1, formated_process_stats/2,
+
+ format/2,
+ error/1, error/2,
+ info/1, info/2
+ ]).
+
+%% ==========================================================================
+
+t() ->
+ os:timestamp().
+
+tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) ->
+ T1 = A1*1000000000+B1*1000+(C1 div 1000),
+ T2 = A2*1000000000+B2*1000+(C2 div 1000),
+ T2 - T1.
+
+formated_timestamp() ->
+ format_timestamp(os:timestamp()).
+
+format_timestamp({_N1, _N2, N3} = TS) ->
+ {_Date, Time} = calendar:now_to_local_time(TS),
+ {Hour,Min,Sec} = Time,
+ FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w.4~w",
+ [Hour, Min, Sec, round(N3/1000)]),
+ lists:flatten(FormatTS).
+
+%% Time is always in number os ms (milli seconds)
+%% At some point, we should convert this to a more readable format...
+format_time(T) when (T < 1000) ->
+ format("~w ms", [T]);
+format_time(T) ->
+ format("~w sec (~w ms)", [T div 1000, T]).
+
+
+formated_process_stats(Pid) ->
+ formated_process_stats("", Pid).
+
+formated_process_stats(Prefix, Pid) when is_list(Prefix) andalso is_pid(Pid) ->
+ try
+ begin
+ TotHeapSz = pi(Pid, total_heap_size),
+ HeapSz = pi(Pid, heap_size),
+ StackSz = pi(Pid, stack_size),
+ Reds = pi(Pid, reductions),
+ GCInfo = pi(Pid, garbage_collection),
+ MinBinVHeapSz = proplists:get_value(min_bin_vheap_size, GCInfo),
+ MinHeapSz = proplists:get_value(min_heap_size, GCInfo),
+ MinGCS = proplists:get_value(minor_gcs, GCInfo),
+ format("~n ~sTotal Heap Size: ~p"
+ "~n ~sHeap Size: ~p"
+ "~n ~sStack Size: ~p"
+ "~n ~sReductions: ~p"
+ "~n ~s[GC] Min Bin VHeap Size: ~p"
+ "~n ~s[GC] Min Heap Size: ~p"
+ "~n ~s[GC] Minor GCS: ~p",
+ [Prefix, TotHeapSz,
+ Prefix, HeapSz,
+ Prefix, StackSz,
+ Prefix, Reds,
+ Prefix, MinBinVHeapSz,
+ Prefix, MinHeapSz,
+ Prefix, MinGCS])
+ end
+ catch
+ _:_:_ ->
+ ""
+ end.
+
+
+pi(Pid, Item) ->
+ {Item, Info} = process_info(Pid, Item),
+ Info.
+
+
+
+%% ==========================================================================
+
+format(F, A) ->
+ lists:flatten(io_lib:format(F, A)).
+
+error(F) ->
+ error(F, []).
+
+error(F, A) ->
+ print(get(sname), "<ERROR> " ++ F, A).
+
+info(F) ->
+ info(F, []).
+
+info(F, A) ->
+ print(get(sname), "<INFO> " ++ F, A).
+
+print(undefined, F, A) ->
+ print("- ", F, A);
+print(Prefix, F, A) ->
+ io:format("[~s, ~s] " ++ F ++ "~n", [formated_timestamp(), Prefix |A]).
+
diff --git a/lib/kernel/test/socket_test_ttest_tcp_client.erl b/lib/kernel/test/socket_test_ttest_tcp_client.erl
new file mode 100644
index 0000000000..f28819ca69
--- /dev/null
+++ b/lib/kernel/test/socket_test_ttest_tcp_client.erl
@@ -0,0 +1,687 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%% ==========================================================================
+%%
+%% This is the "simple" client using gen_tcp. The client is supposed to be
+%% as simple as possible in order to incur as little overhead as possible.
+%%
+%% There are three ways to run the client: active, passive or active-once.
+%%
+%% The client is the entity that controls the test, timing and counting.
+%%
+%% ==========================================================================
+%%
+%% Before the actual test starts, the client performs a "warmup".
+%% The warmup has two functions. First, to ensure that everything is "loaded"
+%% and, second, to calculate an approximate roundtrip time, in order to
+%% "know" how many iterations we should make (to run for the expected time).
+%% This is not intended to be exact, but just to ensure that all tests take
+%% approx the same time to run.
+%%
+%% ==========================================================================
+
+-module(socket_test_ttest_tcp_client).
+
+-export([
+ %% These are for the test suite
+ start_monitor/5, start_monitor/6, start_monitor/8,
+
+ %% These are for starting in a shell when run "manually"
+ start/3, start/4, start/6, start/7,
+ stop/1
+ ]).
+
+%% Internal exports
+-export([
+ do_start/9
+ ]).
+
+-include_lib("kernel/include/inet.hrl").
+-include("socket_test_ttest.hrl").
+-include("socket_test_ttest_client.hrl").
+
+-define(RECV_TIMEOUT, 10000).
+-define(MAX_OUTSTANDING_DEFAULT_1, 100).
+-define(MAX_OUTSTANDING_DEFAULT_2, 10).
+-define(MAX_OUTSTANDING_DEFAULT_3, 3).
+
+-define(LIB, socket_test_ttest_lib).
+-define(I(F), ?LIB:info(F)).
+-define(I(F,A), ?LIB:info(F, A)).
+-define(E(F,A), ?LIB:error(F, A)).
+-define(F(F,A), ?LIB:format(F, A)).
+-define(FORMAT_TIME(T), ?LIB:format_time(T)).
+-define(T(), ?LIB:t()).
+-define(TDIFF(T1,T2), ?LIB:tdiff(T1, T2)).
+
+-type active() :: once | boolean().
+-type msg_id() :: 1..3.
+-type max_outstanding() :: pos_integer().
+-type runtime() :: pos_integer().
+
+
+%% ==========================================================================
+
+start_monitor(Node, Notify, Transport, ServerInfo, Active) ->
+ start_monitor(Node, Notify, Transport, ServerInfo, Active, ?MSG_ID_DEFAULT).
+
+start_monitor(Node, Notify, Transport, ServerInfo, Active, 1 = MsgID) ->
+ start_monitor(Node, Notify, Transport, ServerInfo, Active, MsgID,
+ ?MAX_OUTSTANDING_DEFAULT_1, ?RUNTIME_DEFAULT);
+start_monitor(Node, Notify, Transport, ServerInfo, Active, 2 = MsgID) ->
+ start_monitor(Node, Notify, Transport, ServerInfo, Active, MsgID,
+ ?MAX_OUTSTANDING_DEFAULT_2, ?RUNTIME_DEFAULT);
+start_monitor(Node, Notify, Transport, ServerInfo, Active, 3 = MsgID) ->
+ start_monitor(Node, Notify, Transport, ServerInfo, Active, MsgID,
+ ?MAX_OUTSTANDING_DEFAULT_3, ?RUNTIME_DEFAULT).
+
+start_monitor(Node, Notify, Transport, ServerInfo, Active,
+ MsgID, MaxOutstanding, RunTime)
+ when (Node =/= node()) ->
+ Args = [false,
+ self(), Notify,
+ Transport, ServerInfo, Active, MsgID, MaxOutstanding, RunTime],
+ case rpc:call(Node, ?MODULE, do_start, Args) of
+ {badrpc, _} = Reason ->
+ {error, Reason};
+ {ok, Pid} when is_pid(Pid) ->
+ MRef = erlang:monitor(process, Pid),
+ {ok, {Pid, MRef}};
+ {error, _} = ERROR ->
+ ERROR
+ end;
+start_monitor(_, Notify, Transport, ServerInfo, Active,
+ MsgID, MaxOutstanding, RunTime) ->
+ case do_start(false,
+ self(), Notify,
+ Transport, Active, ServerInfo,
+ MsgID, MaxOutstanding, RunTime) of
+ {ok, Pid} ->
+ MRef = erlang:monitor(process, Pid),
+ {ok, {Pid, MRef}};
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+start(Transport, ServerInfo, Active) ->
+ start(Transport, ServerInfo, Active, ?MSG_ID_DEFAULT).
+
+start(Transport, ServerInfo, Active, 1 = MsgID) ->
+ start(false,
+ Transport, ServerInfo, Active, MsgID,
+ ?MAX_OUTSTANDING_DEFAULT_1, ?RUNTIME_DEFAULT);
+start(Transport, ServerInfo, Active, 2 = MsgID) ->
+ start(false,
+ Transport, ServerInfo, Active, MsgID,
+ ?MAX_OUTSTANDING_DEFAULT_2, ?RUNTIME_DEFAULT);
+start(Transport, ServerInfo, Active, 3 = MsgID) ->
+ start(false,
+ Transport, ServerInfo, Active, MsgID,
+ ?MAX_OUTSTANDING_DEFAULT_3, ?RUNTIME_DEFAULT).
+
+start(Transport, ServerInfo, Active, MsgID, MaxOutstanding, RunTime) ->
+ start(false,
+ Transport, ServerInfo, Active, MsgID, MaxOutstanding, RunTime).
+
+start(Quiet, Transport, ServerInfo, Active, MsgID, MaxOutstanding, RunTime) ->
+ Notify = fun(R) -> present_results(R) end,
+ do_start(Quiet,
+ self(), Notify,
+ Transport, ServerInfo, Active, MsgID, MaxOutstanding, RunTime).
+
+
+-spec do_start(Quiet,
+ Parent,
+ Notify,
+ Transport,
+ ServerInfo,
+ Active,
+ MsgID,
+ MaxOutstanding,
+ RunTime) -> {ok, Pid} | {error, Reason} when
+ Quiet :: boolean(),
+ Parent :: pid(),
+ Notify :: function(),
+ Transport :: atom() | tuple(),
+ ServerInfo :: {inet:ip_address(), inet:port_number()} | string(),
+ Active :: active(),
+ MsgID :: msg_id(),
+ MaxOutstanding :: max_outstanding(),
+ RunTime :: runtime(),
+ Pid :: pid(),
+ Reason :: term().
+
+do_start(Quiet,
+ Parent, Notify,
+ Transport, ServerInfo, Active, MsgID, MaxOutstanding, RunTime)
+ when is_boolean(Quiet) andalso
+ is_pid(Parent) andalso
+ is_function(Notify) andalso
+ (is_atom(Transport) orelse is_tuple(Transport)) andalso
+ (is_boolean(Active) orelse (Active =:= once)) andalso
+ (is_tuple(ServerInfo) orelse is_list(ServerInfo)) andalso
+ (is_integer(MsgID) andalso (MsgID >= 1) andalso (MsgID =< 3)) andalso
+ (is_integer(MaxOutstanding) andalso (MaxOutstanding > 0)) andalso
+ (is_integer(RunTime) andalso (RunTime > 0)) ->
+ Starter = self(),
+ Init = fun() -> put(sname, "client"),
+ init(Quiet,
+ Starter,
+ Parent,
+ Notify,
+ Transport, Active, ServerInfo,
+ MsgID, MaxOutstanding, RunTime)
+ end,
+ {Pid, MRef} = spawn_monitor(Init),
+ receive
+ {'DOWN', MRef, process, Pid, Reason} ->
+ {error, Reason};
+ {?MODULE, Pid, ok} ->
+ erlang:demonitor(MRef),
+ {ok, Pid};
+ {?MODULE, Pid, {error, _} = ERROR} ->
+ erlang:demonitor(MRef, [flush]),
+ ERROR
+ end.
+
+
+%% We should not normally stop this (it terminates when its done).
+stop(Pid) when is_pid(Pid) ->
+ req(Pid, stop).
+
+
+%% ==========================================================================
+
+init(Quiet,
+ Starter,
+ Parent, Notify,
+ Transport, Active, ServerInfo,
+ MsgID, MaxOutstanding, RunTime) ->
+ if
+ not Quiet ->
+ ?I("init with"
+ "~n Transport: ~p"
+ "~n Active: ~p"
+ "~n ServerInfo: ~s"
+ "~n Msg ID: ~p (=> 16 + ~w bytes)"
+ "~n Max Outstanding: ~p"
+ "~n (Suggested) Run Time: ~p ms",
+ [Transport, Active,
+ case ServerInfo of
+ {Addr, Port} ->
+ ?F("Addr: ~s, Port: ~w", [inet:ntoa(Addr), Port]);
+ Path ->
+ Path
+ end,
+ MsgID, size(which_msg_data(MsgID)), MaxOutstanding, RunTime]);
+ true ->
+ ok
+ end,
+ {Mod, Connect} = process_transport(Transport),
+ case Connect(ServerInfo) of
+ {ok, Sock} ->
+ if not Quiet -> ?I("connected");
+ true -> ok
+ end,
+ Starter ! {?MODULE, self(), ok},
+ initial_activation(Mod, Sock, Active),
+ Results = loop(#{quiet => Quiet,
+ slogan => run,
+ runtime => RunTime,
+ start => ?T(),
+ parent => Parent,
+ mod => Mod,
+ sock => Sock,
+ active => Active,
+ msg_data => which_msg_data(MsgID),
+ outstanding => 0,
+ max_outstanding => MaxOutstanding,
+ sid => 1,
+ rid => 1,
+ scnt => 0,
+ rcnt => 0,
+ bcnt => 0,
+ num => undefined,
+ acc => <<>>}),
+ Notify(Results),
+ (catch Mod:close(Sock)),
+ exit(normal);
+ {error, Reason} ->
+ ?E("connect failed: ~p"
+ "~n ~p", [Reason, ServerInfo]),
+ exit({connect, Reason, ServerInfo})
+ end.
+
+process_transport(Mod) when is_atom(Mod) ->
+ %% In this case we assume it to be a plain tcp socket
+ {Mod, fun({A, P}) -> Mod:connect(A, P) end};
+process_transport({Mod, #{domain := Domain} = Opts}) ->
+ Connect =
+ case Domain of
+ local -> fun(Path) -> Mod:connect(Path, Opts) end;
+ _ -> fun({A, P}) -> Mod:connect(A, P, Opts) end
+ end,
+ {Mod, Connect}.
+
+
+which_msg_data(1) -> ?MSG_DATA1;
+which_msg_data(2) -> ?MSG_DATA2;
+which_msg_data(3) -> ?MSG_DATA3.
+
+
+present_results(#{status := ok,
+ runtime := RunTime,
+ bcnt := ByteCnt,
+ cnt := NumIterations}) ->
+ ?I("Results: "
+ "~n Run Time: ~s"
+ "~n ByteCnt: ~s"
+ "~n NumIterations: ~s",
+ [?FORMAT_TIME(RunTime),
+ if ((ByteCnt =:= 0) orelse (RunTime =:= 0)) ->
+ ?F("~w, ~w", [ByteCnt, RunTime]);
+ true ->
+ ?F("~p => ~p byte / ms", [ByteCnt, ByteCnt div RunTime])
+ end,
+ if (RunTime =:= 0) ->
+ "-";
+ true ->
+ ?F("~p => ~p iterations / ms",
+ [NumIterations, NumIterations div RunTime])
+ end]),
+ ok;
+present_results(#{status := Failure,
+ runtime := RunTime,
+ sid := SID,
+ rid := RID,
+ scnt := SCnt,
+ rcnt := RCnt,
+ bcnt := BCnt,
+ num := Num}) ->
+ ?I("Time Test failed: "
+ "~n ~p"
+ "~n"
+ "~nwhen"
+ "~n"
+ "~n Run Time: ~s"
+ "~n Send ID: ~p"
+ "~n Recv ID: ~p"
+ "~n Send Count: ~p"
+ "~n Recv Count: ~p"
+ "~n Byte Count: ~p"
+ "~n Num Iterations: ~p",
+ [Failure,
+ ?FORMAT_TIME(RunTime),
+ SID, RID, SCnt, RCnt, BCnt, Num]).
+
+
+
+loop(#{runtime := RunTime} = State) ->
+ erlang:start_timer(RunTime, self(), stop),
+ try do_loop(State)
+ catch
+ throw:Results ->
+ Results
+ end.
+
+do_loop(State) ->
+ do_loop( handle_message( msg_exchange(State) ) ).
+
+msg_exchange(#{rcnt := Num, num := Num} = State) ->
+ finish(ok, State);
+msg_exchange(#{scnt := Num, num := Num} = State) ->
+ %% We are done sending more requests - now we will just await
+ %% the replies for the (still) outstanding replies.
+ msg_exchange( recv_reply(State) );
+msg_exchange(#{outstanding := Outstanding,
+ max_outstanding := MaxOutstanding} = State)
+ when (Outstanding < MaxOutstanding) ->
+ msg_exchange( send_request(State) );
+msg_exchange(State) ->
+ send_request( recv_reply(State) ).
+
+
+finish(ok,
+ #{start := Start, bcnt := BCnt, num := Num}) ->
+ Stop = ?T(),
+ throw(#{status => ok,
+ runtime => ?TDIFF(Start, Stop),
+ bcnt => BCnt,
+ cnt => Num});
+finish(Reason,
+ #{start := Start,
+ sid := SID, rid := RID,
+ scnt := SCnt, rcnt := RCnt, bcnt := BCnt,
+ num := Num}) ->
+ Stop = ?T(),
+ throw(#{status => Reason,
+ runtime => ?TDIFF(Start, Stop),
+ sid => SID,
+ rid => RID,
+ scnt => SCnt,
+ rcnt => RCnt,
+ bcnt => BCnt,
+ num => Num}).
+
+send_request(#{mod := Mod,
+ sock := Sock,
+ sid := ID,
+ scnt := Cnt,
+ outstanding := Outstanding,
+ max_outstanding := MaxOutstanding,
+ msg_data := Data} = State)
+ when (MaxOutstanding > Outstanding) ->
+ SZ = size(Data),
+ Req = <<?TTEST_TAG:32,
+ ?TTEST_TYPE_REQUEST:32,
+ ID:32,
+ SZ:32,
+ Data/binary>>,
+ case Mod:send(Sock, Req) of
+ ok ->
+ State#{sid => next_id(ID),
+ scnt => Cnt + 1,
+ outstanding => Outstanding + 1};
+ {error, Reason} ->
+ ?E("Failed sending request: ~p", [Reason]),
+ exit({send, Reason})
+ end;
+send_request(State) ->
+ State.
+
+
+
+recv_reply(#{mod := Mod,
+ sock := Sock,
+ rid := ID,
+ active := false,
+ bcnt := BCnt,
+ rcnt := Cnt,
+ outstanding := Outstanding} = State) ->
+ case recv_reply_message1(Mod, Sock, ID) of
+ {ok, MsgSz} ->
+ State#{rid => next_id(ID),
+ bcnt => BCnt + MsgSz,
+ rcnt => Cnt + 1,
+ outstanding => Outstanding - 1};
+
+ {error, timeout} ->
+ ?I("receive timeout"),
+ State;
+
+ {error, Reason} ->
+ finish(Reason, State)
+ end;
+recv_reply(#{mod := Mod,
+ sock := Sock,
+ rid := ID,
+ active := Active,
+ bcnt := BCnt,
+ scnt := SCnt,
+ rcnt := RCnt,
+ outstanding := Outstanding,
+ acc := Acc} = State) ->
+ case recv_reply_message2(Mod, Sock, ID, Acc) of
+ {ok, {MsgSz, NewAcc}} when is_integer(MsgSz) andalso is_binary(NewAcc) ->
+ maybe_activate(Mod, Sock, Active),
+ State#{rid => next_id(ID),
+ bcnt => BCnt + MsgSz,
+ rcnt => RCnt + 1,
+ outstanding => Outstanding - 1,
+ acc => NewAcc};
+
+ ok ->
+ State;
+
+ {error, stop} ->
+ ?I("receive [~w] -> stop", [Active]),
+ %% This will have the effect that no more requests are sent...
+ State#{num => SCnt, stop_started => ?T()};
+
+ {error, timeout} ->
+ ?I("receive[~w] -> timeout", [Active]),
+ State;
+
+ {error, Reason} ->
+ finish(Reason, State)
+ end.
+
+
+%% This function reads exactly one (reply) message. No more no less.
+recv_reply_message1(Mod, Sock, ID) ->
+ case Mod:recv(Sock, 4*4, ?RECV_TIMEOUT) of
+ {ok, <<?TTEST_TAG:32,
+ ?TTEST_TYPE_REPLY:32,
+ ID:32,
+ SZ:32>> = Hdr} ->
+ %% Receive the ping-pong reply boby
+ case Mod:recv(Sock, SZ, ?RECV_TIMEOUT) of
+ {ok, Data} when (size(Data) =:= SZ) ->
+ {ok, size(Hdr) + size(Data)};
+ {error, Reason2} ->
+ ?E("Failed reading body: "
+ "~n ~p: ~p", [Reason2]),
+ {error, {recv_body, Reason2}}
+ end;
+
+ {ok, <<BadTag:32,
+ BadType:32,
+ BadID:32,
+ BadSZ:32>>} ->
+ {error, {invalid_hdr,
+ {?TTEST_TAG, BadTag},
+ {?TTEST_TYPE_REPLY, BadType},
+ {ID, BadID},
+ BadSZ}};
+ {ok, _InvHdr} ->
+ {error, invalid_hdr};
+
+ {error, Reason1} ->
+ ?E("Feiled reading header: "
+ "~n ~p", [Reason1]),
+ {error, {recv_hdr, Reason1}}
+ end.
+
+
+%% This function first attempts to process the data we have already
+%% accumulated. If that is not enough for a (complete) reply, it
+%% will attempt to receive more.
+recv_reply_message2(Mod, Sock, ID, Acc) ->
+ case process_acc_data(ID, Acc) of
+ ok ->
+ %% No or insufficient data, so get more
+ recv_reply_message3(Mod, Sock, ID, Acc);
+
+ {ok, _} = OK -> % We already had a reply accumulated - no need to read more
+ OK;
+
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+%% This function receives a "chunk" of data, then it tries to extract
+%% one (reply) message from the accumulated and new data (combined).
+recv_reply_message3(_Mod, Sock, ID, Acc) ->
+ receive
+ {timeout, _TRef, stop} ->
+ {error, stop};
+
+ {TagClosed, Sock} when (TagClosed =:= tcp_closed) orelse
+ (TagClosed =:= socket_closed) ->
+ {error, closed};
+
+ {TagErr, Sock, Reason} when (TagErr =:= tcp_error) orelse
+ (TagErr =:= socket_error) ->
+ {error, Reason};
+
+ {Tag, Sock, Msg} when (Tag =:= tcp) orelse
+ (Tag =:= socket) ->
+ process_acc_data(ID, <<Acc/binary, Msg/binary>>)
+
+ after ?RECV_TIMEOUT ->
+ ?I("timeout when"
+ "~n ID: ~p"
+ "~n size(Acc): ~p",
+ [ID, size(Acc)]),
+ %% {error, timeout}
+ recv_reply_message3(_Mod, Sock, ID, Acc)
+ end.
+
+
+process_acc_data(ID, <<?TTEST_TAG:32,
+ ?TTEST_TYPE_REPLY:32,
+ ID:32,
+ SZ:32,
+ Data/binary>>) when (SZ =< size(Data)) ->
+ <<_Body:SZ/binary, Rest/binary>> = Data,
+ {ok, {4*4+SZ, Rest}};
+process_acc_data(ID, <<BadTag:32,
+ BadType:32,
+ BadID:32,
+ BadSZ:32,
+ _Data/binary>>)
+ when ((BadTag =/= ?TTEST_TAG) orelse
+ (BadType =/= ?TTEST_TYPE_REPLY) orelse
+ (BadID =/= ID)) ->
+ {error, {invalid_hdr,
+ {?TTEST_TAG, BadTag},
+ {?TTEST_TYPE_REPLY, BadType},
+ {ID, BadID},
+ BadSZ}};
+%% Not enough for an entire (reply) message
+process_acc_data(_ID, _Data) ->
+ ok.
+
+
+handle_message(#{quiet := Quiet,
+ parent := Parent, sock := Sock, scnt := SCnt} = State) ->
+ receive
+ {timeout, _TRef, stop} ->
+ if not Quiet -> ?I("STOP");
+ true -> ok
+ end,
+ %% This will have the effect that no more requests are sent...
+ State#{num => SCnt, stop_started => ?T()};
+
+ {?MODULE, Ref, Parent, stop} ->
+ %% This *aborts* the test
+ reply(Parent, Ref, ok),
+ exit(normal);
+
+ %% Only when active
+ {TagClosed, Sock, Reason} when (TagClosed =:= tcp_closed) orelse
+ (TagClosed =:= socket_closed) ->
+ %% We should never get this (unless the server crashed)
+ exit({closed, Reason});
+
+ %% Only when active
+ {TagErr, Sock, Reason} when (TagErr =:= tcp_error) orelse
+ (TagErr =:= socket_error) ->
+ exit({error, Reason})
+
+ after 0 ->
+ State
+ end.
+
+
+initial_activation(_Mod, _Sock, false = _Active) ->
+ ok;
+initial_activation(Mod, Sock, Active) ->
+ Mod:active(Sock, Active).
+
+
+maybe_activate(Mod, Sock, once = Active) ->
+ Mod:active(Sock, Active);
+maybe_activate(_, _, _) ->
+ ok.
+
+
+%% ==========================================================================
+
+req(Pid, Req) ->
+ Ref = make_ref(),
+ Pid ! {?MODULE, Ref, Pid, Req},
+ receive
+ {'EXIT', Pid, Reason} ->
+ {error, {exit, Reason}};
+ {?MODULE, Ref, Reply} ->
+ Reply
+ end.
+
+reply(Pid, Ref, Reply) ->
+ Pid ! {?MODULE, Ref, Reply}.
+
+
+%% ==========================================================================
+
+next_id(ID) when (ID < ?MAX_ID) ->
+ ID + 1;
+next_id(_) ->
+ 1.
+
+
+%% ==========================================================================
+
+%% t() ->
+%% os:timestamp().
+
+%% tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) ->
+%% T1 = A1*1000000000+B1*1000+(C1 div 1000),
+%% T2 = A2*1000000000+B2*1000+(C2 div 1000),
+%% T2 - T1.
+
+%% formated_timestamp() ->
+%% format_timestamp(os:timestamp()).
+
+%% format_timestamp({_N1, _N2, N3} = TS) ->
+%% {_Date, Time} = calendar:now_to_local_time(TS),
+%% {Hour,Min,Sec} = Time,
+%% FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w.4~w",
+%% [Hour, Min, Sec, round(N3/1000)]),
+%% lists:flatten(FormatTS).
+
+%% %% Time is always in number os ms (milli seconds)
+%% format_time(T) ->
+%% f("~p", [T]).
+
+
+%% ==========================================================================
+
+%% f(F, A) ->
+%% lists:flatten(io_lib:format(F, A)).
+
+%% %% e(F) ->
+%% %% i("<ERROR> " ++ F).
+
+%% e(F, A) ->
+%% p(get(sname), "<ERROR> " ++ F, A).
+
+%% i(F) ->
+%% i(F, []).
+
+%% i(F, A) ->
+%% p(get(sname), "<INFO> " ++ F, A).
+
+%% p(undefined, F, A) ->
+%% p("- ", F, A);
+%% p(Prefix, F, A) ->
+%% io:format("[~s, ~s] " ++ F ++ "~n", [formated_timestamp(), Prefix |A]).
diff --git a/lib/kernel/test/socket_test_ttest_tcp_client_gen.erl b/lib/kernel/test/socket_test_ttest_tcp_client_gen.erl
new file mode 100644
index 0000000000..65a3a94d38
--- /dev/null
+++ b/lib/kernel/test/socket_test_ttest_tcp_client_gen.erl
@@ -0,0 +1,49 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(socket_test_ttest_tcp_client_gen).
+
+-export([
+ start/2, start/3, start/5, start/6,
+ stop/1
+ ]).
+
+-define(TRANSPORT_MOD, socket_test_ttest_tcp_gen).
+
+start(ServerInfo, Active) ->
+ socket_test_ttest_tcp_client:start(?TRANSPORT_MOD, ServerInfo, Active).
+
+start(ServerInfo, Active, MsgID) ->
+ socket_test_ttest_tcp_client:start(?TRANSPORT_MOD, ServerInfo, Active, MsgID).
+
+start(ServerInfo, Active, MsgID, MaxOutstanding, RunTime) ->
+ socket_test_ttest_tcp_client:start(false,
+ ?TRANSPORT_MOD,
+ ServerInfo, Active,
+ MsgID, MaxOutstanding, RunTime).
+
+start(Quiet, ServerInfo, Active, MsgID, MaxOutstanding, RunTime) ->
+ socket_test_ttest_tcp_client:start(Quiet,
+ ?TRANSPORT_MOD,
+ ServerInfo, Active,
+ MsgID, MaxOutstanding, RunTime).
+
+stop(Pid) ->
+ socket_test_ttest_tcp_client:stop(Pid).
diff --git a/lib/kernel/test/socket_test_ttest_tcp_client_socket.erl b/lib/kernel/test/socket_test_ttest_tcp_client_socket.erl
new file mode 100644
index 0000000000..2fb1242028
--- /dev/null
+++ b/lib/kernel/test/socket_test_ttest_tcp_client_socket.erl
@@ -0,0 +1,116 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(socket_test_ttest_tcp_client_socket).
+
+-export([
+ start/4, start/5, start/7, start/8,
+ stop/1
+ ]).
+
+-define(TRANSPORT_MOD, socket_test_ttest_tcp_socket).
+-define(MOD(D, A, M), {?TRANSPORT_MOD, #{domain => D,
+ async => A,
+ method => M}}).
+
+start(Method, Async, Active, ServerInfo)
+ when is_list(ServerInfo) ->
+ Domain = local,
+ socket_test_ttest_tcp_client:start_monitor(?MOD(Domain, Async, Method),
+ ServerInfo, Active);
+start(Method, Async, Active, ServerInfo = {Addr, _})
+ when is_tuple(Addr) andalso (size(Addr) =:= 4) ->
+ Domain = inet,
+ socket_test_ttest_tcp_client:start_monitor(?MOD(Domain, Async, Method),
+ ServerInfo, Active);
+start(Method, Async, Active, ServerInfo = {Addr, _})
+ when is_tuple(Addr) andalso (size(Addr) =:= 8) ->
+ Domain = inet6,
+ socket_test_ttest_tcp_client:start_monitor(?MOD(Domain, Async, Method),
+ ServerInfo, Active).
+
+start(Method, Async, Active, ServerInfo, MsgID)
+ when is_list(ServerInfo) ->
+ %% This is just a simplification
+ Domain = local,
+ socket_test_ttest_tcp_client:start(?MOD(Domain, Async, Method),
+ ServerInfo, Active, MsgID);
+start(Method, Async, Active, ServerInfo = {Addr, _}, MsgID)
+ when is_tuple(Addr) andalso (size(Addr) =:= 4) ->
+ %% This is just a simplification
+ Domain = inet,
+ socket_test_ttest_tcp_client:start(?MOD(Domain, Async, Method),
+ Active, ServerInfo, MsgID);
+start(Method, Async, Active, ServerInfo = {Addr, _}, MsgID)
+ when is_tuple(Addr) andalso (size(Addr) =:= 8) ->
+ Domain = inet6,
+ socket_test_ttest_tcp_client:start(?MOD(Domain, Async, Method),
+ ServerInfo, Active, MsgID).
+
+start(Method, Async, Active, ServerInfo, MsgID, MaxOutstanding, RunTime)
+ when is_list(ServerInfo) ->
+ Domain = local,
+ socket_test_ttest_tcp_client:start(false,
+ ?MOD(Domain, Async, Method),
+ ServerInfo, Active,
+ MsgID, MaxOutstanding, RunTime);
+start(Method, Async, Active, ServerInfo = {Addr, _},
+ MsgID, MaxOutstanding, RunTime)
+ when is_tuple(Addr) andalso (size(Addr) =:= 4) ->
+ Domain = inet,
+ socket_test_ttest_tcp_client:start(false,
+ ?MOD(Domain, Async, Method),
+ ServerInfo, Active,
+ MsgID, MaxOutstanding, RunTime);
+start(Method, Async, Active, ServerInfo = {Addr, _},
+ MsgID, MaxOutstanding, RunTime)
+ when is_tuple(Addr) andalso (size(Addr) =:= 8) ->
+ Domain = inet6,
+ socket_test_ttest_tcp_client:start(false,
+ ?MOD(Domain, Async, Method),
+ ServerInfo, Active,
+ MsgID, MaxOutstanding, RunTime).
+
+start(Quiet, Async, Active, Method, ServerInfo, MsgID, MaxOutstanding, RunTime)
+ when is_list(ServerInfo) ->
+ Domain = local,
+ socket_test_ttest_tcp_client:start(Quiet,
+ ?MOD(Domain, Async, Method),
+ ServerInfo, Active,
+ MsgID, MaxOutstanding, RunTime);
+start(Quiet, Async, Active, Method, ServerInfo = {Addr, _},
+ MsgID, MaxOutstanding, RunTime)
+ when is_tuple(Addr) andalso (size(Addr) =:= 4) ->
+ Domain = inet,
+ socket_test_ttest_tcp_client:start(Quiet,
+ ?MOD(Domain, Async, Method),
+ ServerInfo, Active,
+ MsgID, MaxOutstanding, RunTime);
+start(Quiet, Async, Active, Method, ServerInfo = {Addr, _},
+ MsgID, MaxOutstanding, RunTime)
+ when is_tuple(Addr) andalso (size(Addr) =:= 8) ->
+ Domain = inet6,
+ socket_test_ttest_tcp_client:start(Quiet,
+ ?MOD(Domain, Async, Method),
+ ServerInfo, Active,
+ MsgID, MaxOutstanding, RunTime).
+
+stop(Pid) ->
+ socket_test_ttest_client:stop(Pid).
diff --git a/lib/kernel/test/socket_test_ttest_tcp_gen.erl b/lib/kernel/test/socket_test_ttest_tcp_gen.erl
new file mode 100644
index 0000000000..5d20e49359
--- /dev/null
+++ b/lib/kernel/test/socket_test_ttest_tcp_gen.erl
@@ -0,0 +1,137 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(socket_test_ttest_tcp_gen).
+
+-export([
+ accept/1, accept/2,
+ active/2,
+ close/1,
+ connect/2, connect/3,
+ controlling_process/2,
+ listen/0, listen/1, listen/2,
+ peername/1,
+ port/1,
+ recv/2, recv/3,
+ send/2,
+ shutdown/2,
+ sockname/1
+ ]).
+
+
+-define(LIB, socket_test_lib).
+
+%% ==========================================================================
+
+accept(Sock) ->
+ case gen_tcp:accept(Sock) of
+ {ok, NewSock} ->
+ {ok, NewSock};
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+accept(Sock, Timeout) ->
+ case gen_tcp:accept(Sock, Timeout) of
+ {ok, NewSock} ->
+ {ok, NewSock};
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+active(Sock, NewActive)
+ when (is_boolean(NewActive) orelse (NewActive =:= once)) ->
+ inet:setopts(Sock, [{active, NewActive}]).
+
+
+close(Sock) ->
+ gen_tcp:close(Sock).
+
+
+connect(Addr, Port) ->
+ Opts = [binary, {packet, raw}, {active, false}, {buffer, 32*1024}],
+ do_connect(Addr, Port, Opts).
+
+connect(Addr, Port, #{domain := Domain}) ->
+ Opts = [Domain, binary, {packet, raw}, {active, false}, {buffer, 32*1024}],
+ do_connect(Addr, Port, Opts).
+
+do_connect(Addr, Port, Opts) ->
+ case gen_tcp:connect(Addr, Port, Opts) of
+ {ok, Sock} ->
+ {ok, Sock};
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+controlling_process(Sock, NewPid) ->
+ gen_tcp:controlling_process(Sock, NewPid).
+
+
+%% Create a listen socket
+listen() ->
+ listen(0).
+
+listen(Port) ->
+ listen(Port, #{domain => inet}).
+
+listen(Port, #{domain := Domain}) when is_integer(Port) andalso (Port >= 0) ->
+ case ?LIB:which_local_host_info(Domain) of
+ {ok, #{addr := Addr}} ->
+ Opts = [Domain,
+ binary, {ip, Addr}, {packet, raw}, {active, false},
+ {buffer, 32*1024}],
+ gen_tcp:listen(Port, Opts);
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+peername(Sock) ->
+ inet:peername(Sock).
+
+
+port(Sock) ->
+ inet:port(Sock).
+
+
+recv(Sock, Length) ->
+ gen_tcp:recv(Sock, Length).
+recv(Sock, Length, Timeout) ->
+ gen_tcp:recv(Sock, Length, Timeout).
+
+
+send(Sock, Data) ->
+ gen_tcp:send(Sock, Data).
+
+
+shutdown(Sock, How) ->
+ gen_tcp:shutdown(Sock, How).
+
+
+sockname(Sock) ->
+ inet:sockname(Sock).
+
+
+%% ==========================================================================
+
+
+
diff --git a/lib/kernel/test/socket_test_ttest_tcp_server.erl b/lib/kernel/test/socket_test_ttest_tcp_server.erl
new file mode 100644
index 0000000000..2394dc7924
--- /dev/null
+++ b/lib/kernel/test/socket_test_ttest_tcp_server.erl
@@ -0,0 +1,684 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%% ==========================================================================
+%%
+%% This is the "simple" server using gen_tcp. The server is supposed to be
+%% as simple as possible in order to incur as little overhead as possible.
+%%
+%% There are three ways to run the server: active, passive or active-once.
+%%
+%% The server does only two things; accept connnections and then reply
+%% to requests (actually the handler(s) does that). No timing or counting.
+%% That is all done by the clients.
+%%
+%% ==========================================================================
+
+-module(socket_test_ttest_tcp_server).
+
+-export([
+ %% This are for the test suite
+ start_monitor/3,
+
+ %% This are for starting in a shell when run "manually"
+ start/2,
+
+ stop/1
+ ]).
+
+%% Internal exports
+-export([
+ do_start/3
+ ]).
+
+-include_lib("kernel/include/inet.hrl").
+-include("socket_test_ttest.hrl").
+
+-define(ACC_TIMEOUT, 5000).
+-define(RECV_TIMEOUT, 5000).
+
+-define(LIB, socket_test_ttest_lib).
+-define(I(F), ?LIB:info(F)).
+-define(I(F,A), ?LIB:info(F, A)).
+-define(E(F,A), ?LIB:error(F, A)).
+-define(F(F,A), ?LIB:format(F, A)).
+-define(FORMAT_TIME(T), ?LIB:format_time(T)).
+-define(T(), ?LIB:t()).
+-define(TDIFF(T1,T2), ?LIB:tdiff(T1, T2)).
+
+
+%% ==========================================================================
+
+start_monitor(Node, Transport, Active) when (Node =/= node()) ->
+ case rpc:call(Node, ?MODULE, do_start, [self(), Transport, Active]) of
+ {badrpc, _} = Reason ->
+ {error, Reason};
+ {ok, {Pid, AddrPort}} ->
+ MRef = erlang:monitor(process, Pid),
+ {ok, {{Pid, MRef}, AddrPort}};
+ {error, _} = ERROR ->
+ ERROR
+ end;
+start_monitor(_, Transport, Active) ->
+ case do_start(self(), Transport, Active) of
+ {ok, {Pid, AddrPort}} ->
+ MRef = erlang:monitor(process, Pid),
+ {ok, {{Pid, MRef}, AddrPort}};
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+
+start(Transport, Active) ->
+ do_start(self(), Transport, Active).
+
+%% Note that the Async option is actually only "used" for the
+%% socket transport module (it details how to implement the
+%% active feature).
+do_start(Parent, Transport, Active)
+ when is_pid(Parent) andalso
+ (is_atom(Transport) orelse is_tuple(Transport)) andalso
+ (is_boolean(Active) orelse (Active =:= once)) ->
+ Starter = self(),
+ ServerInit = fun() ->
+ put(sname, "server"),
+ server_init(Starter, Parent, Transport, Active)
+ end,
+ {Pid, MRef} = spawn_monitor(ServerInit),
+ receive
+ {'DOWN', MRef, process, Pid, Reason} ->
+ {error, Reason};
+ {?MODULE, Pid, {ok, AddrPort}} ->
+ erlang:demonitor(MRef),
+ {ok, {Pid, AddrPort}};
+ {?MODULE, Pid, {error, _} = ERROR} ->
+ erlang:demonitor(MRef, [flush]),
+ ERROR
+ end.
+
+
+stop(Pid) when is_pid(Pid) ->
+ req(Pid, stop).
+
+
+%% ==========================================================================
+
+server_init(Starter, Parent, Transport, Active) ->
+ ?I("init with"
+ "~n Transport: ~p"
+ "~n Active: ~p", [Transport, Active]),
+ {Mod, Listen, StatsInterval} = process_transport(Transport, Active),
+ case Listen(0) of
+ {ok, LSock} ->
+ case Mod:port(LSock) of
+ {ok, PortOrPath} ->
+ Result =
+ if
+ is_integer(PortOrPath) ->
+ %% This is just for convenience
+ case Mod:sockname(LSock) of
+ {ok, {Addr, _}} ->
+ ?I("listening on:"
+ "~n Addr: ~p (~s)"
+ "~n Port: ~w"
+ "~n", [Addr,
+ inet:ntoa(Addr),
+ PortOrPath]),
+ {Addr, PortOrPath};
+ {error, SNReason} ->
+ exit({sockname, SNReason})
+ end;
+ is_list(PortOrPath) ->
+ ?I("listening on:"
+ "~n Path: ~s"
+ "~n", [PortOrPath]),
+ PortOrPath
+ end,
+ Starter ! {?MODULE, self(), {ok, Result}},
+ server_loop(#{parent => Parent,
+ mod => Mod,
+ active => Active,
+ lsock => LSock,
+ port_or_path => PortOrPath,
+ handlers => [],
+ stats_interval => StatsInterval,
+ %% Accumulation
+ runtime => 0,
+ mcnt => 0,
+ bcnt => 0,
+ hcnt => 0
+ });
+ {error, PReason} ->
+ (catch Mod:close(LSock)),
+ exit({port, PReason})
+ end;
+ {error, LReason} ->
+ exit({listen, LReason})
+ end.
+
+process_transport(Mod, _) when is_atom(Mod) ->
+ {Mod, fun(Port) -> Mod:listen(Port) end, infinity};
+process_transport({Mod, #{stats_interval := T} = Opts}, Active)
+ when (Active =/= false) ->
+ {Mod, fun(Port) -> Mod:listen(Port, Opts#{stats_to => self()}) end, T};
+process_transport({Mod, #{stats_interval := T} = Opts}, _Active) ->
+ {Mod, fun(Port) -> Mod:listen(Port, Opts) end, T};
+process_transport({Mod, Opts}, _Active) ->
+ {Mod, fun(Port) -> Mod:listen(Port, Opts) end, infinity}.
+
+
+
+server_loop(State) ->
+ server_loop( server_handle_message( server_accept(State, ?ACC_TIMEOUT), 0) ).
+
+server_accept(#{mod := Mod, lsock := LSock} = State, Timeout) ->
+ case Mod:accept(LSock, Timeout) of
+ {ok, Sock} ->
+ server_handle_accepted(State, Sock);
+ {error, timeout} when (Timeout =/= nowait) ->
+ State;
+ {error, AReason} ->
+ (catch Mod:close(LSock)),
+ exit({accept, AReason})
+ end.
+
+%% server_accept(#{mod := Mod,
+%% lsock := LSock} = State) ->
+%% case Mod:accept(LSock, ?ACC_TIMEOUT) of
+%% {ok, Sock} ->
+%% server_handle_accepted(State, Sock);
+%% {error, timeout} ->
+%% State;
+%% {error, AReason} ->
+%% (catch Mod:close(LSock)),
+%% exit({accept, AReason})
+%% end.
+
+server_handle_accepted(#{mod := Mod,
+ lsock := LSock,
+ active := Active,
+ handlers := Handlers} = State,
+ Sock) ->
+ ?I("accepted connection from ~s",
+ [case Mod:peername(Sock) of
+ {ok, Peer} ->
+ format_peername(Peer);
+ {error, _} ->
+ "-"
+ end]),
+ {Pid, _} = handler_start(),
+ ?I("handler ~p started -> try transfer socket control", [Pid]),
+ case Mod:controlling_process(Sock, Pid) of
+ ok ->
+ maybe_start_stats_timer(State, Pid),
+ ?I("server-accept: handler ~p started", [Pid]),
+ handler_continue(Pid, Mod, Sock, Active),
+ Handlers2 = [Pid | Handlers],
+ State#{handlers => Handlers2};
+ {error, CPReason} ->
+ (catch Mod:close(Sock)),
+ (catch Mod:close(LSock)),
+ exit({controlling_process, CPReason})
+ end.
+
+
+format_peername({Addr, Port}) ->
+ case inet:gethostbyaddr(Addr) of
+ {ok, #hostent{h_name = N}} ->
+ ?F("~s (~s:~w)", [N, inet:ntoa(Addr), Port]);
+ {error, _} ->
+ ?F("~p, ~p", [Addr, Port])
+ end;
+format_peername(Path) when is_list(Path) ->
+ Path.
+
+maybe_start_stats_timer(#{active := Active, stats_interval := Time}, Handler)
+ when (Active =/= false) andalso (is_integer(Time) andalso (Time > 0)) ->
+ start_stats_timer(Time, "handler", Handler);
+maybe_start_stats_timer(_, _) ->
+ ok.
+
+start_stats_timer(Time, ProcStr, Pid) ->
+ erlang:start_timer(Time, self(), {stats, Time, ProcStr, Pid}).
+
+server_handle_message(#{mod := Mod,
+ lsock := LSock,
+ parent := Parent,
+ handlers := H} = State, Timeout) ->
+ receive
+ {timeout, _TRef, {stats, Interval, ProcStr, Pid}} ->
+ case server_handle_stats(ProcStr, Pid) of
+ ok ->
+ start_stats_timer(Interval, ProcStr, Pid);
+ skip ->
+ ok
+ end,
+ State;
+
+ {?MODULE, Ref, Parent, stop} ->
+ reply(Parent, Ref, ok),
+ lists:foreach(fun(P) -> handler_stop(P) end, H),
+ (catch Mod:close(LSock)),
+ exit(normal);
+
+ {'DOWN', _MRef, process, Pid, Reason} ->
+ server_handle_down(Pid, Reason, State)
+
+ after Timeout ->
+ State
+ end.
+
+server_handle_stats(ProcStr, Pid) ->
+ case ?LIB:formated_process_stats(Pid) of
+ "" ->
+ skip;
+ FormatedStats ->
+ ?I("Statistics for ~s ~p:~s", [ProcStr, Pid, FormatedStats]),
+ ok
+ end.
+
+
+server_handle_down(Pid, Reason, #{handlers := Handlers} = State) ->
+ case lists:delete(Pid, Handlers) of
+ Handlers ->
+ ?I("unknown process ~p died", [Pid]),
+ State;
+ Handlers2 ->
+ server_handle_handler_down(Pid, Reason, State#{handlers => Handlers2})
+ end.
+
+
+server_handle_handler_down(Pid,
+ {done, RunTime, MCnt, BCnt},
+ #{runtime := AccRunTime,
+ mcnt := AccMCnt,
+ bcnt := AccBCnt,
+ hcnt := AccHCnt} = State) ->
+ AccRunTime2 = AccRunTime + RunTime,
+ AccMCnt2 = AccMCnt + MCnt,
+ AccBCnt2 = AccBCnt + BCnt,
+ AccHCnt2 = AccHCnt + 1,
+ MsgCount2Str =
+ fun(RT, ART, MC, AMC) when (RT > 0) ->
+ ?F("~w => ~w (~w) msgs / ms", [MC, MC div RT, AMC div ART]);
+ (_, _, MC, AMC) ->
+ ?F("~w (~w)", [MC, AMC])
+ end,
+ ByteCount2Str =
+ fun(RT, ART, BC, ABC) when (RT > 0) ->
+ ?F("~w => ~w (~w) bytes / ms", [BC, BC div RT, ABC div ART]);
+ (_, _, BC, ABC) ->
+ ?F("~w", [BC, ABC])
+ end,
+ ?I("handler ~p (~w) done: "
+ "~n Run Time: ~s"
+ "~n Message Count: ~s"
+ "~n Byte Count: ~s",
+ [Pid, AccHCnt2,
+ ?FORMAT_TIME(RunTime),
+ MsgCount2Str(RunTime, AccRunTime2, MCnt, AccMCnt2),
+ ByteCount2Str(RunTime, AccRunTime2, BCnt, AccBCnt2)]),
+ State#{runtime => AccRunTime2,
+ mcnt => AccMCnt2,
+ bcnt => AccBCnt2,
+ hcnt => AccHCnt2};
+server_handle_handler_down(Pid, Reason, State) ->
+ ?I("handler ~p terminated: "
+ "~n ~p", [Pid, Reason]),
+ State.
+
+
+
+%% ==========================================================================
+
+handler_start() ->
+ Self = self(),
+ HandlerInit = fun() -> put(sname, "handler"), handler_init(Self) end,
+ spawn_monitor(HandlerInit).
+
+handler_continue(Pid, Mod, Sock, Active) ->
+ req(Pid, {continue, Mod, Sock, Active}).
+
+handler_stop(Pid) ->
+ req(Pid, stop).
+
+handler_init(Parent) ->
+ ?I("starting"),
+ receive
+ {?MODULE, Ref, Parent, {continue, Mod, Sock, Active}} ->
+ ?I("received continue"),
+ reply(Parent, Ref, ok),
+ handler_initial_activation(Mod, Sock, Active),
+ handler_loop(#{parent => Parent,
+ mod => Mod,
+ sock => Sock,
+ active => Active,
+ start => ?T(),
+ mcnt => 0,
+ bcnt => 0,
+ last_reply => none,
+ acc => <<>>})
+
+ after 5000 ->
+ ?I("timeout when message queue: "
+ "~n ~p"
+ "~nwhen"
+ "~n Parent: ~p", [process_info(self(), messages), Parent]),
+ handler_init(Parent)
+ end.
+
+handler_loop(State) ->
+ handler_loop( handler_handle_message( handler_recv_message(State) ) ).
+
+%% When passive, we read *one* request and then attempt to reply to it.
+handler_recv_message(#{mod := Mod,
+ sock := Sock,
+ active := false,
+ mcnt := MCnt,
+ bcnt := BCnt,
+ last_reply := LID} = State) ->
+ case handler_recv_message2(Mod, Sock) of
+ {ok, {MsgSz, ID, Body}} ->
+ handler_send_reply(Mod, Sock, ID, Body),
+ State#{mcnt => MCnt + 1,
+ bcnt => BCnt + MsgSz,
+ last_reply => ID};
+ {error, closed} ->
+ handler_done(State);
+ {error, timeout} ->
+ ?I("timeout when: "
+ "~n MCnt: ~p"
+ "~n BCnt: ~p"
+ "~n LID: ~p", [MCnt, BCnt, LID]),
+ State
+ end;
+
+
+%% When "active" (once or true), we receive one data "message", which may
+%% contain any number of requests or only part of a request. Then we
+%% process this data together with whatever we had "accumulated" from
+%% prevous messages. Each request will be extracted and replied to. If
+%% there is some data left, not enough for a complete request, we store
+%% this in 'acc' (accumulate it).
+handler_recv_message(#{mod := Mod,
+ sock := Sock,
+ active := Active,
+ mcnt := MCnt,
+ bcnt := BCnt,
+ last_reply := LID,
+ acc := Acc} = State) ->
+ case handler_recv_message3(Mod, Sock, Acc, LID) of
+ {ok, {MCnt2, BCnt2, LID2}, NewAcc} ->
+ handler_maybe_activate(Mod, Sock, Active),
+ State#{mcnt => MCnt + MCnt2,
+ bcnt => BCnt + BCnt2,
+ last_reply => LID2,
+ acc => NewAcc};
+
+ {error, closed} ->
+ if
+ (size(Acc) =:= 0) ->
+ handler_done(State);
+ true ->
+ ?E("client done with partial message: "
+ "~n Last Reply Sent: ~w"
+ "~n Message Count: ~w"
+ "~n Byte Count: ~w"
+ "~n Partial Message: ~w bytes",
+ [LID, MCnt, BCnt, size(Acc)]),
+ exit({closed_with_partial_message, LID})
+ end;
+
+ {error, timeout} ->
+ ?I("timeout when: "
+ "~n MCnt: ~p"
+ "~n BCnt: ~p"
+ "~n LID: ~p"
+ "~n size(Acc): ~p", [MCnt, BCnt, LID, size(Acc)]),
+ State
+ end.
+
+handler_process_data(Acc, Mod, Sock, LID) ->
+ handler_process_data(Acc, Mod, Sock, 0, 0, LID).
+
+%% Extract each complete request, one at a time.
+handler_process_data(<<?TTEST_TAG:32,
+ ?TTEST_TYPE_REQUEST:32,
+ ID:32,
+ SZ:32,
+ Rest/binary>>,
+ Mod, Sock,
+ MCnt, BCnt, _LID) when (size(Rest) >= SZ) ->
+ <<Body:SZ/binary, Rest2/binary>> = Rest,
+ case handler_send_reply(Mod, Sock, ID, Body) of
+ ok ->
+ handler_process_data(Rest2, Mod, Sock, MCnt+1, BCnt+16+SZ, ID);
+ {error, _} = ERROR ->
+ ERROR
+ end;
+handler_process_data(Data, _Mod, _Sock, MCnt, BCnt, LID) ->
+ {ok, {MCnt, BCnt, LID}, Data}.
+
+
+handler_recv_message2(Mod, Sock) ->
+ case Mod:recv(Sock, 4*4, ?RECV_TIMEOUT) of
+ {ok, <<?TTEST_TAG:32,
+ ?TTEST_TYPE_REQUEST:32,
+ ID:32,
+ SZ:32>> = Hdr} ->
+ case Mod:recv(Sock, SZ, ?RECV_TIMEOUT) of
+ {ok, Body} when (SZ =:= size(Body)) ->
+ {ok, {size(Hdr) + size(Body), ID, Body}};
+ {error, BReason} ->
+ ?E("failed reading body (~w) of message ~w:"
+ "~n ~p", [SZ, ID, BReason]),
+ exit({recv, body, ID, SZ, BReason})
+ end;
+ {error, timeout} = ERROR ->
+ ERROR;
+ {error, closed} = ERROR ->
+ ERROR;
+ {error, HReason} ->
+ ?E("Failed reading header of message:"
+ "~n ~p", [HReason]),
+ exit({recv, header, HReason})
+ end.
+
+
+handler_recv_message3(Mod, Sock, Acc, LID) ->
+ receive
+ {TagClosed, Sock} when (TagClosed =:= tcp_closed) orelse
+ (TagClosed =:= socket_closed) ->
+ {error, closed};
+
+ {TagErr, Sock, Reason} when (TagErr =:= tcp_error) orelse
+ (TagErr =:= socket_error) ->
+ {error, Reason};
+
+ {Tag, Sock, Msg} when (Tag =:= tcp) orelse
+ (Tag =:= socket) ->
+ handler_process_data(<<Acc/binary, Msg/binary>>, Mod, Sock, LID)
+
+ after ?RECV_TIMEOUT ->
+ {error, timeout}
+ end.
+
+
+
+handler_send_reply(Mod, Sock, ID, Data) ->
+ SZ = size(Data),
+ Msg = <<?TTEST_TAG:32,
+ ?TTEST_TYPE_REPLY:32,
+ ID:32,
+ SZ:32,
+ Data/binary>>,
+ case Mod:send(Sock, Msg) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ (catch Mod:close(Sock)),
+ exit({send, Reason})
+ end.
+
+
+handler_done(State) ->
+ handler_done(State, ?T()).
+
+handler_done(#{start := Start,
+ mod := Mod,
+ sock := Sock,
+ mcnt := MCnt,
+ bcnt := BCnt}, Stop) ->
+ (catch Mod:close(Sock)),
+ exit({done, ?TDIFF(Start, Stop), MCnt, BCnt}).
+
+
+handler_handle_message(#{parent := Parent} = State) ->
+ receive
+ {'EXIT', Parent, Reason} ->
+ exit({parent_exit, Reason})
+ after 0 ->
+ State
+ end.
+
+
+handler_initial_activation(_Mod, _Sock, false = _Active) ->
+ ok;
+handler_initial_activation(Mod, Sock, Active) ->
+ Mod:active(Sock, Active).
+
+
+handler_maybe_activate(Mod, Sock, once = Active) ->
+ Mod:active(Sock, Active);
+handler_maybe_activate(_, _, _) ->
+ ok.
+
+
+
+%% ==========================================================================
+
+%% which_addr() ->
+%% case inet:getifaddrs() of
+%% {ok, IfAddrs} ->
+%% which_addrs(inet, IfAddrs);
+%% {error, Reason} ->
+%% exit({getifaddrs, Reason})
+%% end.
+
+%% which_addrs(_Family, []) ->
+%% exit({getifaddrs, not_found});
+%% which_addrs(Family, [{"lo", _} | IfAddrs]) ->
+%% %% Skip
+%% which_addrs(Family, IfAddrs);
+%% which_addrs(Family, [{"docker" ++ _, _} | IfAddrs]) ->
+%% %% Skip docker
+%% which_addrs(Family, IfAddrs);
+%% which_addrs(Family, [{"br-" ++ _, _} | IfAddrs]) ->
+%% %% Skip docker
+%% which_addrs(Family, IfAddrs);
+%% which_addrs(Family, [{"en" ++ _, IfOpts} | IfAddrs]) ->
+%% %% Maybe take this one
+%% case which_addr(Family, IfOpts) of
+%% {ok, Addr} ->
+%% Addr;
+%% error ->
+%% which_addrs(Family, IfAddrs)
+%% end;
+%% which_addrs(Family, [{_IfName, IfOpts} | IfAddrs]) ->
+%% case which_addr(Family, IfOpts) of
+%% {ok, Addr} ->
+%% Addr;
+%% error ->
+%% which_addrs(Family, IfAddrs)
+%% end.
+
+%% which_addr(_, []) ->
+%% error;
+%% which_addr(inet, [{addr, Addr}|_])
+%% when is_tuple(Addr) andalso (size(Addr) =:= 4) ->
+%% {ok, Addr};
+%% which_addr(inet6, [{addr, Addr}|_])
+%% when is_tuple(Addr) andalso (size(Addr) =:= 8) ->
+%% {ok, Addr};
+%% which_addr(Family, [_|IfOpts]) ->
+%% which_addr(Family, IfOpts).
+
+
+%% ==========================================================================
+
+req(Pid, Req) ->
+ Ref = make_ref(),
+ Pid ! {?MODULE, Ref, self(), Req},
+ receive
+ {'EXIT', Pid, Reason} ->
+ {error, {exit, Reason}};
+ {?MODULE, Ref, Reply} ->
+ Reply
+ end.
+
+reply(Pid, Ref, Reply) ->
+ Pid ! {?MODULE, Ref, Reply}.
+
+
+%% ==========================================================================
+
+%% t() ->
+%% os:timestamp().
+
+%% tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) ->
+%% T1 = A1*1000000000+B1*1000+(C1 div 1000),
+%% T2 = A2*1000000000+B2*1000+(C2 div 1000),
+%% T2 - T1.
+
+%% formated_timestamp() ->
+%% format_timestamp(os:timestamp()).
+
+%% format_timestamp({_N1, _N2, N3} = TS) ->
+%% {_Date, Time} = calendar:now_to_local_time(TS),
+%% {Hour,Min,Sec} = Time,
+%% FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w.4~w",
+%% [Hour, Min, Sec, round(N3/1000)]),
+%% lists:flatten(FormatTS).
+
+%% %% Time is always in number os ms (milli seconds)
+%% format_time(T) ->
+%% f("~p", [T]).
+
+
+%% ==========================================================================
+
+%% f(F, A) ->
+%% lists:flatten(io_lib:format(F, A)).
+
+%% e(F, A) ->
+%% p(get(sname), "<ERROR> " ++ F, A).
+
+%% i(F) ->
+%% i(F, []).
+
+%% i(F, A) ->
+%% p(get(sname), "<INFO> " ++ F, A).
+
+%% p(undefined, F, A) ->
+%% p("- ", F, A);
+%% p(Prefix, F, A) ->
+%% io:format("[~s, ~s] " ++ F ++ "~n", [formated_timestamp(), Prefix |A]).
+
diff --git a/lib/kernel/test/socket_test_ttest_tcp_server_gen.erl b/lib/kernel/test/socket_test_ttest_tcp_server_gen.erl
new file mode 100644
index 0000000000..fdf40f1369
--- /dev/null
+++ b/lib/kernel/test/socket_test_ttest_tcp_server_gen.erl
@@ -0,0 +1,39 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(socket_test_ttest_tcp_server_gen).
+
+-export([
+ start/1, start/2,
+ stop/1
+ ]).
+
+-define(TRANSPORT_MOD, socket_test_ttest_tcp_gen).
+-define(MOD(D), {?TRANSPORT_MOD, #{domain => D}}).
+
+start(Active) ->
+ start(inet, Active).
+
+start(Domain, Active) ->
+ socket_test_ttest_tcp_server:start(?MOD(Domain), Active).
+
+
+stop(Pid) ->
+ socket_test_ttest_tcp_server:stop(Pid).
diff --git a/lib/kernel/test/socket_test_ttest_tcp_server_socket.erl b/lib/kernel/test/socket_test_ttest_tcp_server_socket.erl
new file mode 100644
index 0000000000..4045bf4e4e
--- /dev/null
+++ b/lib/kernel/test/socket_test_ttest_tcp_server_socket.erl
@@ -0,0 +1,46 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(socket_test_ttest_tcp_server_socket).
+
+-export([
+ start/4,
+ stop/1
+ ]).
+
+-define(TRANSPORT_MOD, socket_test_ttest_tcp_socket).
+%% -define(MOD(M), {?TRANSPORT_MOD, #{async => false,
+%% method => M,
+%% stats_interval => 10000}}).
+-define(MOD(D,M,A), {?TRANSPORT_MOD, #{domain => D,
+ async => A,
+ method => M}}).
+
+start(Method, Domain, Async, Active) ->
+ socket_test_ttest_tcp_server:start(?MOD(Domain, Method, Async), Active).
+ %% {ok, {Pid, AddrPort}} ->
+ %% MRef = erlang:monitor(process, Pid),
+ %% {ok, {Pid, MRef, AddrPort}};
+ %% {error, _} = ERROR ->
+ %% ERROR
+ %% end.
+
+stop(Pid) ->
+ socket_test_ttest_tcp_server:stop(Pid).
diff --git a/lib/kernel/test/socket_test_ttest_tcp_socket.erl b/lib/kernel/test/socket_test_ttest_tcp_socket.erl
new file mode 100644
index 0000000000..a1e08e605c
--- /dev/null
+++ b/lib/kernel/test/socket_test_ttest_tcp_socket.erl
@@ -0,0 +1,724 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(socket_test_ttest_tcp_socket).
+
+-export([
+ accept/1, accept/2,
+ active/2,
+ close/1,
+ connect/1, connect/2, connect/3,
+ controlling_process/2,
+ listen/0, listen/1, listen/2,
+ port/1,
+ peername/1,
+ recv/2, recv/3,
+ send/2,
+ shutdown/2,
+ sockname/1
+ ]).
+
+
+-define(LIB, socket_test_lib).
+
+-define(READER_RECV_TIMEOUT, 1000).
+
+-define(DATA_MSG(Sock, Method, Data),
+ {socket,
+ #{sock => Sock, reader => self(), method => Method},
+ Data}).
+
+-define(CLOSED_MSG(Sock, Method),
+ {socket_closed,
+ #{sock => Sock, reader => self(), method => Method}}).
+
+-define(ERROR_MSG(Sock, Method, Reason),
+ {socket_error,
+ #{sock => Sock, reader => self(), method => Method},
+ Reason}).
+
+
+%% ==========================================================================
+
+%% This does not really work. Its just a placeholder for the time being...
+
+%% getopt(Sock, Opt) when is_atom(Opt) ->
+%% socket:getopt(Sock, socket, Opt).
+
+%% setopt(Sock, Opt, Value) when is_atom(Opt) ->
+%% socket:setopts(Sock, socket, Opt, Value).
+
+
+%% ==========================================================================
+
+%% The way we use server async its no point in doing a async accept call
+%% (we do never actually run the test with more than one client).
+accept(#{sock := LSock, opts := #{async := Async,
+ method := Method} = Opts}) ->
+ case socket:accept(LSock) of
+ {ok, Sock} ->
+ Self = self(),
+ Reader = spawn(fun() ->
+ reader_init(Self, Sock, Async, false, Method)
+ end),
+ maybe_start_stats_timer(Opts, Reader),
+ {ok, #{sock => Sock, reader => Reader, method => Method}};
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+%% If a timeout has been explictly specified, then we do not use
+%% async here. We will pass it on to the reader process.
+accept(#{sock := LSock, opts := #{async := Async,
+ method := Method} = Opts}, Timeout) ->
+ case socket:accept(LSock, Timeout) of
+ {ok, Sock} ->
+ Self = self(),
+ Reader = spawn(fun() ->
+ reader_init(Self, Sock, Async, false, Method)
+ end),
+ maybe_start_stats_timer(Opts, Reader),
+ {ok, #{sock => Sock, reader => Reader, method => Method}};
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+active(#{reader := Pid}, NewActive)
+ when (is_boolean(NewActive) orelse (NewActive =:= once)) ->
+ Pid ! {?MODULE, active, NewActive},
+ ok.
+
+
+close(#{sock := Sock, reader := Pid}) ->
+ Pid ! {?MODULE, stop},
+ Unlink = case socket:sockname(Sock) of
+ {ok, #{family := local, path := Path}} ->
+ fun() -> os:cmd("unlink " ++ Path), ok end;
+ _ ->
+ fun() -> ok end
+ end,
+ Res = socket:close(Sock),
+ Unlink(),
+ Res.
+
+%% Create a socket and connect it to a peer
+connect(ServerPath) when is_list(ServerPath) ->
+ Domain = local,
+ ClientPath = mk_unique_path(),
+ LocalSA = #{family => Domain,
+ path => ClientPath},
+ ServerSA = #{family => Domain, path => ServerPath},
+ Opts = #{domain => Domain,
+ proto => default,
+ method => plain},
+ Cleanup = fun() -> os:cmd("unlink " ++ ClientPath), ok end,
+ do_connect(LocalSA, ServerSA, Cleanup, Opts).
+
+connect(Addr, Port) when is_tuple(Addr) andalso is_integer(Port) ->
+ Domain = inet,
+ LocalSA = any,
+ ServerSA = #{family => Domain,
+ addr => Addr,
+ port => Port},
+ Opts = #{domain => Domain,
+ proto => tcp,
+ method => plain},
+ Cleanup = fun() -> ok end,
+ do_connect(LocalSA, ServerSA, Cleanup, Opts);
+connect(ServerPath,
+ #{domain := local = Domain} = Opts)
+ when is_list(ServerPath) ->
+ ClientPath = mk_unique_path(),
+ LocalSA = #{family => Domain,
+ path => ClientPath},
+ ServerSA = #{family => Domain,
+ path => ServerPath},
+ Cleanup = fun() -> os:cmd("unlink " ++ ClientPath), ok end,
+ do_connect(LocalSA, ServerSA, Cleanup, Opts#{proto => default}).
+
+connect(Addr, Port, #{domain := Domain} = Opts) ->
+ LocalSA = any,
+ ServerSA = #{family => Domain,
+ addr => Addr,
+ port => Port},
+ Cleanup = fun() -> ok end,
+ do_connect(LocalSA, ServerSA, Cleanup, Opts#{proto => tcp}).
+
+do_connect(LocalSA, ServerSA, Cleanup, #{domain := Domain,
+ proto := Proto,
+ async := Async,
+ method := Method} = Opts) ->
+ try
+ begin
+ Sock =
+ case socket:open(Domain, stream, Proto) of
+ {ok, S} ->
+ S;
+ {error, OReason} ->
+ throw({error, {open, OReason}})
+ end,
+ case socket:bind(Sock, LocalSA) of
+ {ok, _} ->
+ ok;
+ {error, BReason} ->
+ (catch socket:close(Sock)),
+ Cleanup(),
+ throw({error, {bind, BReason}})
+ end,
+ case socket:connect(Sock, ServerSA) of
+ ok ->
+ ok;
+ {error, CReason} ->
+ (catch socket:close(Sock)),
+ Cleanup(),
+ throw({error, {connect, CReason}})
+ end,
+ Self = self(),
+ Reader = spawn(fun() ->
+ reader_init(Self, Sock, Async, false, Method)
+ end),
+ maybe_start_stats_timer(Opts, Reader),
+ {ok, #{sock => Sock, reader => Reader, method => Method}}
+ end
+ catch
+ throw:ERROR:_ ->
+ ERROR
+ end.
+
+mk_unique_path() ->
+ [NodeName | _] = string:tokens(atom_to_list(node()), [$@]),
+ ?LIB:f("/tmp/esock_~s_~w", [NodeName, erlang:system_time(nanosecond)]).
+
+maybe_start_stats_timer(#{stats_to := Pid,
+ stats_interval := T},
+ Reader) when is_pid(Pid) ->
+ erlang:start_timer(T, Pid, {stats, T, "reader", Reader});
+maybe_start_stats_timer(_O, _) ->
+ ok.
+
+controlling_process(#{sock := Sock, reader := Pid}, NewPid) ->
+ case socket:setopt(Sock, otp, controlling_process, NewPid) of
+ ok ->
+ Pid ! {?MODULE, self(), controlling_process, NewPid},
+ receive
+ {?MODULE, Pid, controlling_process} ->
+ ok
+ end;
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+%% Create a listen socket
+listen() ->
+ listen(0).
+
+listen(Port) when is_integer(Port) ->
+ listen(Port, #{domain => inet, async => false, method => plain});
+listen(Path) when is_list(Path) ->
+ listen(Path, #{domain => local, async => false, method => plain}).
+
+listen(0, #{domain := local} = Opts) ->
+ listen(mk_unique_path(), Opts);
+listen(Path, #{domain := local = Domain} = Opts)
+ when is_list(Path) andalso (Path =/= []) ->
+ SA = #{family => Domain,
+ path => Path},
+ Cleanup = fun() -> os:cmd("unlink " ++ Path), ok end,
+ do_listen(SA, Cleanup, Opts#{proto => default});
+listen(Port, #{domain := Domain} = Opts)
+ when is_integer(Port) andalso (Port >= 0) ->
+ %% Bind fills in the rest
+ case ?LIB:which_local_host_info(Domain) of
+ {ok, #{addr := Addr}} ->
+ SA = #{family => Domain,
+ addr => Addr,
+ port => Port},
+ Cleanup = fun() -> ok end,
+ do_listen(SA, Cleanup, Opts#{proto => tcp});
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+do_listen(SA,
+ Cleanup,
+ #{domain := Domain, proto := Proto,
+ async := Async, method := Method} = Opts)
+ when (Method =:= plain) orelse (Method =:= msg) andalso
+ is_boolean(Async) ->
+ try
+ begin
+ Sock = case socket:open(Domain, stream, Proto) of
+ {ok, S} ->
+ S;
+ {error, OReason} ->
+ throw({error, {open, OReason}})
+ end,
+ case socket:bind(Sock, SA) of
+ {ok, _} ->
+ ok;
+ {error, BReason} ->
+ (catch socket:close(Sock)),
+ Cleanup(),
+ throw({error, {bind, BReason}})
+ end,
+ case socket:listen(Sock) of
+ ok ->
+ ok;
+ {error, LReason} ->
+ (catch socket:close(Sock)),
+ Cleanup(),
+ throw({error, {listen, LReason}})
+ end,
+ {ok, #{sock => Sock, opts => Opts}}
+ end
+ catch
+ throw:{error, Reason}:_ ->
+ {error, Reason}
+ end.
+
+
+port(#{sock := Sock}) ->
+ case socket:sockname(Sock) of
+ {ok, #{family := local, path := Path}} ->
+ {ok, Path};
+ {ok, #{port := Port}} ->
+ {ok, Port};
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+peername(#{sock := Sock}) ->
+ case socket:peername(Sock) of
+ {ok, #{family := local, path := Path}} ->
+ {ok, Path};
+ {ok, #{addr := Addr, port := Port}} ->
+ {ok, {Addr, Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+recv(#{sock := Sock, method := plain}, Length) ->
+ socket:recv(Sock, Length);
+recv(#{sock := Sock, method := msg}, Length) ->
+ case socket:recvmsg(Sock, Length, 0, [], infinity) of
+ {ok, #{iov := [Bin]}} ->
+ {ok, Bin};
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+recv(#{sock := Sock, method := plain}, Length, Timeout) ->
+ socket:recv(Sock, Length, Timeout);
+recv(#{sock := Sock, method := msg}, Length, Timeout) ->
+ case socket:recvmsg(Sock, Length, 0, [], Timeout) of
+ {ok, #{iov := [Bin]}} ->
+ {ok, Bin};
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+send(#{sock := Sock, method := plain}, Bin) ->
+ socket:send(Sock, Bin);
+send(#{sock := Sock, method := msg}, Bin) ->
+ socket:sendmsg(Sock, #{iov => [Bin]}).
+
+
+shutdown(#{sock := Sock}, How) ->
+ socket:shutdown(Sock, How).
+
+
+sockname(#{sock := Sock}) ->
+ case socket:sockname(Sock) of
+ {ok, #{addr := Addr, port := Port}} ->
+ {ok, {Addr, Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+%% ==========================================================================
+
+reader_init(ControllingProcess, Sock, Async, Active, Method)
+ when is_pid(ControllingProcess) andalso
+ is_boolean(Async) andalso
+ (is_boolean(Active) orelse (Active =:= once)) andalso
+ ((Method =:= plain) orelse (Method =:= msg)) ->
+ put(verbose, false),
+ MRef = erlang:monitor(process, ControllingProcess),
+ reader_loop(#{ctrl_proc => ControllingProcess,
+ ctrl_proc_mref => MRef,
+ async => Async,
+ select_info => undefined,
+ select_num => 0, % Count the number of select messages
+ active => Active,
+ sock => Sock,
+ method => Method}).
+
+
+%% Never read
+reader_loop(#{active := false,
+ ctrl_proc := Pid} = State) ->
+ receive
+ {?MODULE, stop} ->
+ reader_exit(State, stop);
+
+ {?MODULE, Pid, controlling_process, NewPid} ->
+ OldMRef = maps:get(ctrl_proc_mref, State),
+ erlang:demonitor(OldMRef, [flush]),
+ NewMRef = erlang:monitor(process, NewPid),
+ Pid ! {?MODULE, self(), controlling_process},
+ reader_loop(State#{ctrl_proc => NewPid,
+ ctrl_proc_mref => NewMRef});
+
+ {?MODULE, active, NewActive} ->
+ reader_loop(State#{active => NewActive});
+
+ {'DOWN', MRef, process, Pid, Reason} ->
+ case maps:get(ctrl_proc_mref, State) of
+ MRef ->
+ reader_exit(State, {ctrl_exit, Reason});
+ _ ->
+ reader_loop(State)
+ end
+ end;
+
+%% Read *once* and then change to false
+reader_loop(#{active := once,
+ async := false,
+ sock := Sock,
+ method := Method,
+ ctrl_proc := Pid} = State) ->
+ case do_recv(Method, Sock) of
+ {ok, Data} ->
+ Pid ! ?DATA_MSG(Sock, Method, Data),
+ reader_loop(State#{active => false});
+ {error, timeout} ->
+ receive
+ {?MODULE, stop} ->
+ reader_exit(State, stop);
+
+ {?MODULE, Pid, controlling_process, NewPid} ->
+ OldMRef = maps:get(ctrl_proc_mref, State),
+ erlang:demonitor(OldMRef, [flush]),
+ NewMRef = erlang:monitor(process, NewPid),
+ Pid ! {?MODULE, self(), controlling_process},
+ reader_loop(State#{ctrl_proc => NewPid,
+ ctrl_proc_mref => NewMRef});
+
+ {?MODULE, active, NewActive} ->
+ reader_loop(State#{active => NewActive});
+
+ {'DOWN', MRef, process, Pid, Reason} ->
+ case maps:get(ctrl_proc_mref, State) of
+ MRef ->
+ reader_exit(State, {ctrl_exit, Reason});
+ _ ->
+ reader_loop(State)
+ end
+ after 0 ->
+ reader_loop(State)
+ end;
+
+ {error, closed} = E1 ->
+ Pid ! ?CLOSED_MSG(Sock, Method),
+ reader_exit(State, E1);
+
+ {error, Reason} = E2 ->
+ Pid ! ?ERROR_MSG(Sock, Method, Reason),
+ reader_exit(State, E2)
+ end;
+reader_loop(#{active := once,
+ async := true,
+ select_info := undefined,
+ sock := Sock,
+ method := Method,
+ ctrl_proc := Pid} = State) ->
+ case do_recv(Method, Sock, nowait) of
+ {select, SelectInfo} ->
+ reader_loop(State#{select_info => SelectInfo});
+ {ok, Data} ->
+ Pid ! ?DATA_MSG(Sock, Method, Data),
+ reader_loop(State#{active => false});
+
+ {error, closed} = E1 ->
+ Pid ! ?CLOSED_MSG(Sock, Method),
+ reader_exit(State, E1);
+
+ {error, Reason} = E2 ->
+ Pid ! ?ERROR_MSG(Sock, Method, Reason),
+ reader_exit(State, E2)
+ end;
+reader_loop(#{active := once,
+ async := true,
+ select_info := {select_info, _, Ref},
+ select_num := N,
+ sock := Sock,
+ method := Method,
+ ctrl_proc := Pid} = State) ->
+ receive
+ {?MODULE, stop} ->
+ reader_exit(State, stop);
+
+ {?MODULE, Pid, controlling_process, NewPid} ->
+ OldMRef = maps:get(ctrl_proc_mref, State),
+ erlang:demonitor(OldMRef, [flush]),
+ NewMRef = erlang:monitor(process, NewPid),
+ Pid ! {?MODULE, self(), controlling_process},
+ reader_loop(State#{ctrl_proc => NewPid,
+ ctrl_proc_mref => NewMRef});
+
+ {?MODULE, active, NewActive} ->
+ reader_loop(State#{active => NewActive});
+
+ {'DOWN', MRef, process, Pid, Reason} ->
+ case maps:get(ctrl_proc_mref, State) of
+ MRef ->
+ reader_exit(State, {ctrl_exit, Reason});
+ _ ->
+ reader_loop(State)
+ end;
+
+ {'$socket', Sock, select, Ref} ->
+ case do_recv(Method, Sock, nowait) of
+ {ok, Data} when is_binary(Data) ->
+ Pid ! ?DATA_MSG(Sock, Method, Data),
+ reader_loop(State#{active => false,
+ select_info => undefined,
+ select_num => N+1});
+
+ {error, closed} = E1 ->
+ Pid ! ?CLOSED_MSG(Sock, Method),
+ reader_exit(State, E1);
+
+ {error, Reason} = E2 ->
+ Pid ! ?ERROR_MSG(Sock, Method, Reason),
+ reader_exit(State, E2)
+ end
+ end;
+
+%% Read and forward data continuously
+reader_loop(#{active := true,
+ async := false,
+ sock := Sock,
+ method := Method,
+ ctrl_proc := Pid} = State) ->
+ case do_recv(Method, Sock) of
+ {ok, Data} ->
+ Pid ! ?DATA_MSG(Sock, Method, Data),
+ reader_loop(State);
+ {error, timeout} ->
+ receive
+ {?MODULE, stop} ->
+ reader_exit(State, stop);
+
+ {?MODULE, Pid, controlling_process, NewPid} ->
+ OldMRef = maps:get(ctrl_proc_mref, State),
+ erlang:demonitor(OldMRef, [flush]),
+ NewMRef = erlang:monitor(process, NewPid),
+ Pid ! {?MODULE, self(), controlling_process},
+ reader_loop(State#{ctrl_proc => NewPid,
+ ctrl_proc_mref => NewMRef});
+
+ {?MODULE, active, NewActive} ->
+ reader_loop(State#{active => NewActive});
+
+ {'DOWN', MRef, process, Pid, Reason} ->
+ case maps:get(ctrl_proc_mref, State) of
+ MRef ->
+ reader_exit(State, {ctrl_exit, Reason});
+ _ ->
+ reader_loop(State)
+ end
+ after 0 ->
+ reader_loop(State)
+ end;
+
+ {error, closed} = E1 ->
+ Pid ! ?CLOSED_MSG(Sock, Method),
+ reader_exit(State, E1);
+
+ {error, Reason} = E2 ->
+ Pid ! ?ERROR_MSG(Sock, Method, Reason),
+ reader_exit(State, E2)
+ end;
+reader_loop(#{active := true,
+ async := true,
+ select_info := undefined,
+ sock := Sock,
+ method := Method,
+ ctrl_proc := Pid} = State) ->
+ case do_recv(Method, Sock) of
+ {select, SelectInfo} ->
+ reader_loop(State#{select_info => SelectInfo});
+ {ok, Data} ->
+ Pid ! ?DATA_MSG(Sock, Method, Data),
+ reader_loop(State);
+
+ {error, closed} = E1 ->
+ Pid ! ?CLOSED_MSG(Sock, Method),
+ reader_exit(State, E1);
+
+ {error, Reason} = E2 ->
+ Pid ! ?ERROR_MSG(Sock, Method, Reason),
+ reader_exit(State, E2)
+ end;
+reader_loop(#{active := true,
+ async := true,
+ select_info := {select_info, _, Ref},
+ select_num := N,
+ sock := Sock,
+ method := Method,
+ ctrl_proc := Pid} = State) ->
+ receive
+ {?MODULE, stop} ->
+ reader_exit(State, stop);
+
+ {?MODULE, Pid, controlling_process, NewPid} ->
+ OldMRef = maps:get(ctrl_proc_mref, State),
+ erlang:demonitor(OldMRef, [flush]),
+ NewMRef = erlang:monitor(process, NewPid),
+ Pid ! {?MODULE, self(), controlling_process},
+ reader_loop(State#{ctrl_proc => NewPid,
+ ctrl_proc_mref => NewMRef});
+
+ {?MODULE, active, NewActive} ->
+ reader_loop(State#{active => NewActive});
+
+ {'DOWN', MRef, process, Pid, Reason} ->
+ case maps:get(ctrl_proc_mref, State) of
+ MRef ->
+ reader_exit(State, {ctrl_exit, Reason});
+ _ ->
+ reader_loop(State)
+ end;
+
+ {'$socket', Sock, select, Ref} ->
+ case do_recv(Method, Sock, nowait) of
+ {ok, Data} when is_binary(Data) ->
+ Pid ! ?DATA_MSG(Sock, Method, Data),
+ reader_loop(State#{select_info => undefined,
+ select_num => N+1});
+
+ {error, closed} = E1 ->
+ Pid ! ?CLOSED_MSG(Sock, Method),
+ reader_exit(State, E1);
+
+ {error, Reason} = E2 ->
+ Pid ! ?ERROR_MSG(Sock, Method, Reason),
+ reader_exit(State, E2)
+ end
+ end.
+
+
+do_recv(Method, Sock) ->
+ do_recv(Method, Sock, ?READER_RECV_TIMEOUT).
+
+do_recv(plain, Sock, Timeout) ->
+ socket:recv(Sock, 0, Timeout);
+do_recv(msg, Sock, Timeout) ->
+ case socket:recvmsg(Sock, 0, 0, [], Timeout) of
+ {ok, #{iov := [Bin]}} ->
+ {ok, Bin};
+ {select, _} = SELECT ->
+ SELECT;
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+reader_exit(#{async := false, active := Active}, stop) ->
+ vp("reader stopped when active: ~w", [Active]),
+ exit(normal);
+reader_exit(#{async := true,
+ active := Active,
+ select_info := SelectInfo,
+ select_num := N}, stop) ->
+ vp("reader stopped when active: ~w"
+ "~n Current select info: ~p"
+ "~n Number of select messages: ~p", [Active, SelectInfo, N]),
+ exit(normal);
+reader_exit(#{async := false, active := Active}, {ctrl_exit, normal}) ->
+ vp("reader ctrl exit when active: ~w", [Active]),
+ exit(normal);
+reader_exit(#{async := true,
+ active := Active,
+ select_info := SelectInfo,
+ select_num := N}, {ctrl_exit, normal}) ->
+ vp("reader ctrl exit when active: ~w"
+ "~n Current select info: ~p"
+ "~n Number of select messages: ~p", [Active, SelectInfo, N]),
+ exit(normal);
+reader_exit(#{async := false, active := Active}, {ctrl_exit, Reason}) ->
+ vp("reader exit when ctrl crash when active: ~w", [Active]),
+ exit({controlling_process, Reason});
+reader_exit(#{async := true,
+ active := Active,
+ select_info := SelectInfo,
+ select_num := N}, {ctrl_exit, Reason}) ->
+ vp("reader exit when ctrl crash when active: ~w"
+ "~n Current select info: ~p"
+ "~n Number of select messages: ~p", [Active, SelectInfo, N]),
+ exit({controlling_process, Reason});
+reader_exit(#{async := false, active := Active}, {error, closed}) ->
+ vp("reader exit when socket closed when active: ~w", [Active]),
+ exit(normal);
+reader_exit(#{async := true,
+ active := Active,
+ select_info := SelectInfo,
+ select_num := N}, {error, closed}) ->
+ vp("reader exit when socket closed when active: ~w "
+ "~n Current select info: ~p"
+ "~n Number of select messages: ~p", [Active, SelectInfo, N]),
+ exit(normal);
+reader_exit(#{async := false, active := Active}, {error, Reason}) ->
+ vp("reader exit when socket error when active: ~w", [Active]),
+ exit(Reason);
+reader_exit(#{async := true,
+ active := Active,
+ select_info := SelectInfo,
+ select_num := N}, {error, Reason}) ->
+ vp("reader exit when socket error when active: ~w: "
+ "~n Current select info: ~p"
+ "~n Number of select messages: ~p", [Active, SelectInfo, N]),
+ exit(Reason).
+
+
+
+
+
+
+%% ==========================================================================
+
+vp(F, A) ->
+ vp(get(verbose), F, A).
+
+vp(true, F, A) ->
+ p(F, A);
+vp(_, _, _) ->
+ ok.
+
+p(F, A) ->
+ io:format(F ++ "~n", A).
+
diff --git a/lib/kernel/vsn.mk b/lib/kernel/vsn.mk
index e578f3dde4..013cc28c40 100644
--- a/lib/kernel/vsn.mk
+++ b/lib/kernel/vsn.mk
@@ -1 +1 @@
-KERNEL_VSN = 6.5.2
+KERNEL_VSN = 7.0
diff --git a/lib/megaco/doc/src/notes.xml b/lib/megaco/doc/src/notes.xml
index 9f9d5a2c23..c2f72e8b2f 100644
--- a/lib/megaco/doc/src/notes.xml
+++ b/lib/megaco/doc/src/notes.xml
@@ -37,7 +37,47 @@
section is the version number of Megaco.</p>
- <section><title>Megaco 3.18.8</title>
+ <section><title>Megaco 3.19</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>Refactored the internal handling of deprecated and
+ removed functions.</p>
+ <p>
+ Own Id: OTP-16469</p>
+ </item>
+ <item>
+ <p>
+ The preliminary version 3 codec(s) prev3a, prev3b and
+ prev3c has been deprecated and will be *removed* in OTP
+ 24. The encoding config option 'version3' will continue
+ to work until OTP 24.</p>
+ <p>
+ Own Id: OTP-16531</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+ <section><title>Megaco 3.18.8.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The mini parser could not properly decode some IPv6
+ addresses.</p>
+ <p>
+ Own Id: OTP-16631 Aux Id: ERIERL-491 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Megaco 3.18.8</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/megaco/src/text/megaco_text_mini_parser.yrl b/lib/megaco/src/text/megaco_text_mini_parser.yrl
index af3050a05b..2a4041867e 100644
--- a/lib/megaco/src/text/megaco_text_mini_parser.yrl
+++ b/lib/megaco/src/text/megaco_text_mini_parser.yrl
@@ -77,7 +77,7 @@ Nonterminals
Terminals
- %% 'AddToken'
+ 'AddToken'
%% 'AndAUDITselectToken'
'AuditCapToken'
'AuditToken'
@@ -106,7 +106,7 @@ Terminals
%% 'EmergencyValueToken'
'ErrorToken'
%% 'EventBufferToken'
- %% 'EventsToken'
+ 'EventsToken'
%% 'ExternalToken'
'FailoverToken'
'ForcedToken'
@@ -273,7 +273,7 @@ pathName -> safeToken : ensure_pathName('$1') .
safeToken -> safeToken2 : make_safe_token('$1') .
-%% safeToken2 -> 'AddToken' : '$1' .
+safeToken2 -> 'AddToken' : '$1' .
safeToken2 -> 'AuditToken' : '$1' .
safeToken2 -> 'AuditCapToken' : '$1' .
safeToken2 -> 'AuditValueToken' : '$1' .
@@ -298,7 +298,7 @@ safeToken2 -> 'EmbedToken' : '$1' .
%% safeToken2 -> 'EmergencyOffToken' : '$1' .
safeToken2 -> 'ErrorToken' : '$1' .
%% safeToken2 -> 'EventBufferToken' : '$1' .
-%% safeToken2 -> 'EventsToken' : '$1' .
+safeToken2 -> 'EventsToken' : '$1' .
%% safeToken2 -> 'ExternalToken' : '$1' . % v3
safeToken2 -> 'FailoverToken' : '$1' .
safeToken2 -> 'ForcedToken' : '$1' .
diff --git a/lib/megaco/test/megaco_codec_mini_SUITE.erl b/lib/megaco/test/megaco_codec_mini_SUITE.erl
index 12113aae70..6c7f60db05 100644
--- a/lib/megaco/test/megaco_codec_mini_SUITE.erl
+++ b/lib/megaco/test/megaco_codec_mini_SUITE.erl
@@ -39,7 +39,32 @@
init_per_testcase/2, end_per_testcase/2,
otp7672_msg01/1,
- otp7672_msg02/1
+ otp7672_msg02/1,
+
+ otp16631_msg01/1,
+ otp16631_msg02/1,
+ otp16631_msg03/1,
+ otp16631_msg04/1,
+ otp16631_msg05/1,
+ otp16631_msg06/1,
+ otp16631_msg11/1,
+ otp16631_msg12/1,
+ otp16631_msg13/1,
+ otp16631_msg14/1,
+ otp16631_msg15/1,
+ otp16631_msg16/1,
+ otp16631_msg21/1,
+ otp16631_msg22/1,
+ otp16631_msg23/1,
+ otp16631_msg24/1,
+ otp16631_msg25/1,
+ otp16631_msg26/1,
+ otp16631_msg31/1,
+ otp16631_msg32/1,
+ otp16631_msg33/1,
+ otp16631_msg34/1,
+ otp16631_msg35/1,
+ otp16631_msg36/1
]).
@@ -62,15 +87,51 @@ all() ->
groups() ->
[
- {tickets, [], tickets_cases()}
+ {tickets, [], tickets_cases()},
+ {otp7672, [], otp7672_cases()},
+ {otp16631, [], otp16631_cases()}
].
tickets_cases() ->
[
+ {group, otp7672},
+ {group, otp16631}
+ ].
+
+otp7672_cases() ->
+ [
otp7672_msg01,
otp7672_msg02
].
+otp16631_cases() ->
+ [
+ otp16631_msg01,
+ otp16631_msg02,
+ otp16631_msg03,
+ otp16631_msg04,
+ otp16631_msg05,
+ otp16631_msg06,
+ otp16631_msg11,
+ otp16631_msg12,
+ otp16631_msg13,
+ otp16631_msg14,
+ otp16631_msg15,
+ otp16631_msg16,
+ otp16631_msg21,
+ otp16631_msg22,
+ otp16631_msg23,
+ otp16631_msg24,
+ otp16631_msg25,
+ otp16631_msg26,
+ otp16631_msg31,
+ otp16631_msg32,
+ otp16631_msg33,
+ otp16631_msg34,
+ otp16631_msg35,
+ otp16631_msg36
+ ].
+
%%
@@ -200,6 +261,342 @@ otp7672(Msg) ->
end.
+
+%% --------------------------------------------------------------
+%%
+
+otp16631_msg01(suite) ->
+ [];
+otp16631_msg01(Config) when is_list(Config) ->
+ d("otp16631_msg01 -> entry", []),
+ ok = otp16631( otp16631_msg01() ),
+ ok.
+
+otp16631_msg01() ->
+ otp16631_msg("a").
+
+
+%% --
+
+otp16631_msg02(suite) ->
+ [];
+otp16631_msg02(Config) when is_list(Config) ->
+ d("otp16631_msg02 -> entry", []),
+ ok = otp16631( otp16631_msg02() ),
+ ok.
+
+otp16631_msg02() ->
+ otp16631_msg("b").
+
+
+%% --
+
+otp16631_msg03(suite) ->
+ [];
+otp16631_msg03(Config) when is_list(Config) ->
+ d("otp16631_msg03 -> entry", []),
+ ok = otp16631( otp16631_msg03() ),
+ ok.
+
+otp16631_msg03() ->
+ otp16631_msg("c").
+
+
+%% --
+
+otp16631_msg04(suite) ->
+ [];
+otp16631_msg04(Config) when is_list(Config) ->
+ d("otp16631_msg04 -> entry", []),
+ ok = otp16631( otp16631_msg04() ),
+ ok.
+
+otp16631_msg04() ->
+ otp16631_msg("d").
+
+
+%% --
+
+otp16631_msg05(suite) ->
+ [];
+otp16631_msg05(Config) when is_list(Config) ->
+ d("otp16631_msg05 -> entry", []),
+ ok = otp16631( otp16631_msg05() ),
+ ok.
+
+otp16631_msg05() ->
+ otp16631_msg("e").
+
+
+%% --
+
+otp16631_msg06(suite) ->
+ [];
+otp16631_msg06(Config) when is_list(Config) ->
+ d("otp16631_msg06 -> entry", []),
+ ok = otp16631( otp16631_msg06() ),
+ ok.
+
+otp16631_msg06() ->
+ otp16631_msg("f").
+
+
+%% --
+
+otp16631_msg11(suite) ->
+ [];
+otp16631_msg11(Config) when is_list(Config) ->
+ d("otp16631_msg11 -> entry", []),
+ ok = otp16631( otp16631_msg11() ),
+ ok.
+
+otp16631_msg11() ->
+ otp16631_msg("000a").
+
+
+%% --
+
+otp16631_msg12(suite) ->
+ [];
+otp16631_msg12(Config) when is_list(Config) ->
+ d("otp16631_msg12 -> entry", []),
+ ok = otp16631( otp16631_msg12() ),
+ ok.
+
+otp16631_msg12() ->
+ otp16631_msg("000b").
+
+
+%% --
+
+otp16631_msg13(suite) ->
+ [];
+otp16631_msg13(Config) when is_list(Config) ->
+ d("otp16631_msg13 -> entry", []),
+ ok = otp16631( otp16631_msg13() ),
+ ok.
+
+otp16631_msg13() ->
+ otp16631_msg("000c").
+
+
+%% --
+
+otp16631_msg14(suite) ->
+ [];
+otp16631_msg14(Config) when is_list(Config) ->
+ d("otp16631_msg14 -> entry", []),
+ ok = otp16631( otp16631_msg14() ),
+ ok.
+
+otp16631_msg14() ->
+ otp16631_msg("000d").
+
+
+%% --
+
+otp16631_msg15(suite) ->
+ [];
+otp16631_msg15(Config) when is_list(Config) ->
+ d("otp16631_msg15 -> entry", []),
+ ok = otp16631( otp16631_msg15() ),
+ ok.
+
+otp16631_msg15() ->
+ otp16631_msg("000e").
+
+
+%% --
+
+otp16631_msg16(suite) ->
+ [];
+otp16631_msg16(Config) when is_list(Config) ->
+ d("otp16631_msg16 -> entry", []),
+ ok = otp16631( otp16631_msg16() ),
+ ok.
+
+otp16631_msg16() ->
+ otp16631_msg("000f").
+
+
+%% --
+
+otp16631_msg21(suite) ->
+ [];
+otp16631_msg21(Config) when is_list(Config) ->
+ d("otp16631_msg21 -> entry", []),
+ ok = otp16631( otp16631_msg21() ),
+ ok.
+
+otp16631_msg21() ->
+ otp16631_msg("0a12").
+
+
+%% --
+
+otp16631_msg22(suite) ->
+ [];
+otp16631_msg22(Config) when is_list(Config) ->
+ d("otp16631_msg22 -> entry", []),
+ ok = otp16631( otp16631_msg22() ),
+ ok.
+
+otp16631_msg22() ->
+ otp16631_msg("0b12").
+
+
+%% --
+
+otp16631_msg23(suite) ->
+ [];
+otp16631_msg23(Config) when is_list(Config) ->
+ d("otp16631_msg23 -> entry", []),
+ ok = otp16631( otp16631_msg23() ),
+ ok.
+
+otp16631_msg23() ->
+ otp16631_msg("0c12").
+
+
+%% --
+
+otp16631_msg24(suite) ->
+ [];
+otp16631_msg24(Config) when is_list(Config) ->
+ d("otp16631_msg24 -> entry", []),
+ ok = otp16631( otp16631_msg24() ),
+ ok.
+
+otp16631_msg24() ->
+ otp16631_msg("0d12").
+
+
+%% --
+
+otp16631_msg25(suite) ->
+ [];
+otp16631_msg25(Config) when is_list(Config) ->
+ d("otp16631_msg25 -> entry", []),
+ ok = otp16631( otp16631_msg25() ),
+ ok.
+
+otp16631_msg25() ->
+ otp16631_msg("0e12").
+
+
+%% --
+
+otp16631_msg26(suite) ->
+ [];
+otp16631_msg26(Config) when is_list(Config) ->
+ d("otp16631_msg26 -> entry", []),
+ ok = otp16631( otp16631_msg26() ),
+ ok.
+
+otp16631_msg26() ->
+ otp16631_msg("0f12").
+
+
+%% --
+
+otp16631_msg31(suite) ->
+ [];
+otp16631_msg31(Config) when is_list(Config) ->
+ d("otp16631_msg31 -> entry", []),
+ ok = otp16631( otp16631_msg31() ),
+ ok.
+
+otp16631_msg31() ->
+ otp16631_msg("a123").
+
+
+%% --
+
+otp16631_msg32(suite) ->
+ [];
+otp16631_msg32(Config) when is_list(Config) ->
+ d("otp16631_msg32 -> entry", []),
+ ok = otp16631( otp16631_msg32() ),
+ ok.
+
+otp16631_msg32() ->
+ otp16631_msg("b123").
+
+
+%% --
+
+otp16631_msg33(suite) ->
+ [];
+otp16631_msg33(Config) when is_list(Config) ->
+ d("otp16631_msg33 -> entry", []),
+ ok = otp16631( otp16631_msg33() ),
+ ok.
+
+otp16631_msg33() ->
+ otp16631_msg("c123").
+
+
+%% --
+
+otp16631_msg34(suite) ->
+ [];
+otp16631_msg34(Config) when is_list(Config) ->
+ d("otp16631_msg34 -> entry", []),
+ ok = otp16631( otp16631_msg34() ),
+ ok.
+
+otp16631_msg34() ->
+ otp16631_msg("d123").
+
+
+%% --
+
+otp16631_msg35(suite) ->
+ [];
+otp16631_msg35(Config) when is_list(Config) ->
+ d("otp16631_msg35 -> entry", []),
+ ok = otp16631( otp16631_msg35() ),
+ ok.
+
+otp16631_msg35() ->
+ otp16631_msg("e123").
+
+
+%% --
+
+otp16631_msg36(suite) ->
+ [];
+otp16631_msg36(Config) when is_list(Config) ->
+ d("otp16631_msg36 -> entry", []),
+ ok = otp16631( otp16631_msg36() ),
+ ok.
+
+otp16631_msg36() ->
+ otp16631_msg("f123").
+
+
+%% -----
+
+otp16631( Msg ) ->
+ Bin = erlang:list_to_binary(Msg),
+ try megaco_compact_text_encoder:decode_mini_message([], dynamic, Bin) of
+ {ok, _} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ catch
+ C:E:S ->
+ {error, {C, E, S}}
+ end.
+
+
+otp16631_msg(X) when is_list(X) ->
+ "!/1 [2409:8050:5005:1243:1011::" ++ X ++
+ "] T=2523{C=-{SC=ROOT{SV{MT=RS,RE=901,PF=ETSI_BGF/2,V=3}}}}".
+
+
+
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
p(F, A) ->
diff --git a/lib/megaco/test/megaco_mess_SUITE.erl b/lib/megaco/test/megaco_mess_SUITE.erl
index 8fb5c4a982..be523f42a2 100644
--- a/lib/megaco/test/megaco_mess_SUITE.erl
+++ b/lib/megaco/test/megaco_mess_SUITE.erl
@@ -266,6 +266,7 @@
]).
-endif.
+-include_lib("common_test/include/ct.hrl").
-include_lib("megaco/include/megaco.hrl").
-include_lib("megaco/include/megaco_message_v1.hrl").
-include("megaco_test_lib.hrl").
@@ -511,18 +512,19 @@ init_per_testcase(Case, Config) ->
init_per_testcase2(otp_7189 = Case, Config) ->
C = lists:keydelete(tc_timeout, 1, Config),
- init_per_testcase3(Case, [{tc_timeout, min(2)} |C]);
+ init_per_testcase3(Case, [{tc_timeout, min(tfactor(2, Config))} |C]);
init_per_testcase2(request_and_no_reply = Case, Config) ->
C = lists:keydelete(tc_timeout, 1, Config),
- init_per_testcase3(Case, [{tc_timeout, min(2)} |C]);
+ init_per_testcase3(Case, [{tc_timeout, min(tfactor(2, Config))} |C]);
init_per_testcase2(Case, Config) ->
C = lists:keydelete(tc_timeout, 1, Config),
- init_per_testcase3(Case, [{tc_timeout, min(1)} |C]).
+ init_per_testcase3(Case, [{tc_timeout, min(tfactor(1, Config))} |C]).
init_per_testcase3(Case, Config) ->
megaco_test_global_sys_monitor:reset_events(),
megaco_test_lib:init_per_testcase(Case, Config).
-
+
+
end_per_testcase(Case, Config) ->
@@ -539,6 +541,13 @@ end_per_testcase(Case, Config) ->
min(M) -> ?MINS(M).
+tfactor(T, Config) ->
+ case ?config(megaco_factor, Config) of
+ Factor when is_integer(Factor) andalso (Factor > 1) ->
+ T * Factor;
+ _ ->
+ T
+ end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -10560,6 +10569,7 @@ otp_6442_resend_request2_mg_notify_request_ar(Rid, Tid, Cid) ->
otp_6442_resend_reply1(suite) ->
[];
otp_6442_resend_reply1(Config) when is_list(Config) ->
+ Factor = ?config(megaco_factor, Config),
Pre = fun() ->
MgNode = make_node_name(mg),
d("start (MG) node: ~p", [MgNode]),
@@ -10567,14 +10577,14 @@ otp_6442_resend_reply1(Config) when is_list(Config) ->
ok = ?START_NODES(Nodes, true),
Nodes
end,
- Case = fun do_otp_6442_resend_reply1/1,
+ Case = fun(Nodes) -> do_otp_6442_resend_reply1(Nodes, Factor) end,
Post = fun(Nodes) ->
d("stop nodes"),
?STOP_NODES(lists:reverse(Nodes))
end,
try_tc(request_and_no_reply, Pre, Case, Post).
-do_otp_6442_resend_reply1([MgNode]) ->
+do_otp_6442_resend_reply1([MgNode], Factor) ->
d("[MG] start the simulator "),
{ok, Mg} = megaco_test_megaco_generator:start_link("MG", MgNode),
@@ -10591,7 +10601,7 @@ do_otp_6442_resend_reply1([MgNode]) ->
{ok, MgId} = megaco_test_megaco_generator:exec(Mg, MgEvSeq),
i("await the transport module service change send_message event"),
- Pid = otp_6442_expect(fun otp_6442_rsrp1_verify_scr_msg/1, 5000),
+ Pid = otp_6442_expect(fun otp_6442_rsrp1_verify_scr_msg/1, Factor * 5000),
i("wait some before issuing the service change reply"),
sleep(500),
@@ -10610,7 +10620,7 @@ do_otp_6442_resend_reply1([MgNode]) ->
i("await the transport module first notify-reply send_message event from MG: "
"ignore"),
- otp_6442_expect(fun otp_6442_rsrp1_verify_first_nr_msg/1, 5000),
+ otp_6442_expect(fun otp_6442_rsrp1_verify_first_nr_msg/1, Factor * 5000),
i("await the transport module second notify-reply send_message event from MG: "
"ack"),
@@ -10955,6 +10965,7 @@ otp_6442_resend_reply1_err_desc(T) ->
otp_6442_resend_reply2(suite) ->
[];
otp_6442_resend_reply2(Config) when is_list(Config) ->
+ Factor = ?config(megaco_factor, Config),
Pre = fun() ->
MgNode = make_node_name(mg),
d("start (MG) node: ~p", [MgNode]),
@@ -10962,14 +10973,14 @@ otp_6442_resend_reply2(Config) when is_list(Config) ->
ok = ?START_NODES(Nodes, true),
Nodes
end,
- Case = fun do_otp_6442_resend_reply2/1,
+ Case = fun(Nodes) -> do_otp_6442_resend_reply2(Nodes, Factor) end,
Post = fun(Nodes) ->
d("stop nodes"),
?STOP_NODES(lists:reverse(Nodes))
end,
try_tc(otp6442rrep2, Pre, Case, Post).
-do_otp_6442_resend_reply2([MgNode]) ->
+do_otp_6442_resend_reply2([MgNode], Factor) ->
d("[MG] start the simulator "),
{ok, Mg} = megaco_test_megaco_generator:start_link("MG", MgNode),
@@ -10986,7 +10997,7 @@ do_otp_6442_resend_reply2([MgNode]) ->
{ok, MgId} = megaco_test_megaco_generator:exec(Mg, MgEvSeq),
i("await the transport module service change send_message event"),
- Pid = otp_6442_expect(fun otp_6442_rsrp2_verify_scr_msg/1, 5000),
+ Pid = otp_6442_expect(fun otp_6442_rsrp2_verify_scr_msg/1, Factor * 5000),
i("wait some before issuing the service change reply"),
sleep(500),
@@ -11004,7 +11015,7 @@ do_otp_6442_resend_reply2([MgNode]) ->
megaco_test_generic_transport:incomming_message(Pid, NotifyRequest),
i("await the transport module notify-reply send_message event from MG: ignore"),
- otp_6442_expect(otp_6442_rsrp2_verify_first_nr_msg_fun(), 5000),
+ otp_6442_expect(otp_6442_rsrp2_verify_first_nr_msg_fun(), Factor * 5000),
i("await the transport module notify-reply resend_message event from MG: ack"),
{TransId, _, _} =
diff --git a/lib/megaco/test/megaco_segment_SUITE.erl b/lib/megaco/test/megaco_segment_SUITE.erl
index 08b86606de..a403c3309d 100644
--- a/lib/megaco/test/megaco_segment_SUITE.erl
+++ b/lib/megaco/test/megaco_segment_SUITE.erl
@@ -165,7 +165,7 @@ end_per_group(_Group, Config) ->
init_per_testcase(Case, Config) ->
process_flag(trap_exit, true),
- p("init_per_suite -> entry with"
+ p("init_per_testcase -> entry with"
"~n Config: ~p"
"~n Nodes: ~p", [Config, erlang:nodes()]),
@@ -175,7 +175,7 @@ init_per_testcase(Case, Config) ->
end_per_testcase(Case, Config) ->
process_flag(trap_exit, false),
- p("end_per_suite -> entry with"
+ p("end_per_testcase -> entry with"
"~n Config: ~p"
"~n Nodes: ~p", [Config, erlang:nodes()]),
@@ -806,17 +806,9 @@ send_segmented_msg_plain2(doc) ->
"Second plain test that it is possible to send segmented messages. "
"Send window = infinity. ";
send_segmented_msg_plain2(Config) when is_list(Config) ->
+ Factor = ?config(megaco_factor, Config),
+ ct:timetrap(?MINS(1) + Factor * ?MINS(1)),
Pre = fun() ->
- %% We leave it commented out as test
- %% All the other changes to the framework
- %% may have "solved" the issues...
-
- %% <CONDITIONAL-SKIP>
- %% Skippable = [{unix, [linux]}],
- %% Condition = fun() -> ?OS_BASED_SKIP(Skippable) end,
- %% ?NON_PC_TC_MAYBE_SKIP(Config, Condition),
- %% </CONDITIONAL-SKIP>
-
MgcNode = make_node_name(mgc),
MgNode = make_node_name(mg),
d("start nodes: "
@@ -827,20 +819,20 @@ send_segmented_msg_plain2(Config) when is_list(Config) ->
ok = ?START_NODES(Nodes),
Nodes
end,
- Case = fun do_send_segmented_msg_plain2/1,
+ Case = fun(X) -> do_send_segmented_msg_plain2(Factor, X) end,
Post = fun(Nodes) ->
d("stop nodes"),
?STOP_NODES(lists:reverse(Nodes))
end,
try_tc(ssmp2, Pre, Case, Post).
-do_send_segmented_msg_plain2([MgcNode, MgNode]) ->
+do_send_segmented_msg_plain2(Factor, [MgcNode, MgNode]) ->
d("[MGC] start the simulator "),
{ok, Mgc} = megaco_test_tcp_generator:start_link("MGC", MgcNode),
d("[MGC] create the event sequence"),
- MgcEvSeq = ssmp2_mgc_event_sequence(text, tcp),
+ MgcEvSeq = ssmp2_mgc_event_sequence(Factor, text, tcp),
i("wait some time before starting the MGC simulation"),
sleep(1000),
@@ -855,7 +847,7 @@ do_send_segmented_msg_plain2([MgcNode, MgNode]) ->
{ok, Mg} = megaco_test_megaco_generator:start_link("MG", MgNode),
d("[MG] create the event sequence"),
- MgEvSeq = ssmp2_mg_event_sequence(text, tcp),
+ MgEvSeq = ssmp2_mg_event_sequence(Factor, text, tcp),
i("wait some time before starting the MG simulation"),
sleep(1000),
@@ -883,7 +875,7 @@ do_send_segmented_msg_plain2([MgcNode, MgNode]) ->
%% MGC generator stuff
%%
-ssmp2_mgc_event_sequence(text, tcp) ->
+ssmp2_mgc_event_sequence(Factor, text, tcp) ->
DecodeFun = ssmp2_mgc_decode_msg_fun(megaco_pretty_text_encoder, []),
EncodeFun = ssmp2_mgc_encode_msg_fun(megaco_pretty_text_encoder, []),
Mid = {deviceName,"mgc"},
@@ -908,6 +900,7 @@ ssmp2_mgc_event_sequence(text, tcp) ->
SegmentRep1 = ssmp2_mgc_segment_reply_msg(Mid, TransId, 1, false),
SegmentRep2 = ssmp2_mgc_segment_reply_msg(Mid, TransId, 2, true),
TransAck = ssmp2_mgc_trans_ack_msg(Mid, TransId),
+ TO = fun(T) -> Factor * T end,
EvSeq = [{debug, true},
{decode, DecodeFun},
{encode, EncodeFun},
@@ -915,15 +908,17 @@ ssmp2_mgc_event_sequence(text, tcp) ->
{expect_accept, any},
{expect_receive, "service-change-request", {ScrVerifyFun, 5000}},
{send, "service-change-reply", ServiceChangeRep},
- {expect_nothing, timer:seconds(1)},
+ {expect_nothing, ?SECS(1)},
{send, "notify request", NotifyReq},
- {expect_receive, "notify reply: segment 1", {NrVerifyFun1, 2000}},
+ {expect_receive, "notify reply: segment 1",
+ {NrVerifyFun1, TO(?SECS(2))}},
{send, "segment reply 1", SegmentRep1},
- {expect_receive, "notify reply: segment 2", {NrVerifyFun2, 1000}},
+ {expect_receive, "notify reply: segment 2",
+ {NrVerifyFun2, TO(?SECS(1))}},
{send, "segment reply 2", SegmentRep2},
{sleep, 100}, % {expect_nothing, 500},
{send, "transaction-ack", TransAck},
- {expect_closed, timer:seconds(5)},
+ {expect_closed, TO(?SECS(5))},
disconnect
],
EvSeq.
@@ -1036,14 +1031,13 @@ ssmp2_mgc_verify_notify_reply_segment_msg_fun(SN, Last,
ssmp2_mgc_verify_notify_reply_segment(#'MegacoMessage'{mess = Mess} = M,
SN, Last, TransId, TermId, Cid) ->
- io:format("ssmp2_mgc_verify_notify_reply_segment -> entry with"
- "~n M: ~p"
- "~n SN: ~p"
- "~n Last: ~p"
- "~n TransId: ~p"
- "~n TermId: ~p"
- "~n Cid: ~p"
- "~n", [M, SN, Last, TransId, TermId, Cid]),
+ p("ssmp2_mgc_verify_notify_reply_segment -> entry with"
+ "~n M: ~p"
+ "~n SN: ~p"
+ "~n Last: ~p"
+ "~n TransId: ~p"
+ "~n TermId: ~p"
+ "~n Cid: ~p", [M, SN, Last, TransId, TermId, Cid]),
Body =
case Mess of
#'Message'{version = ?VERSION,
@@ -1173,7 +1167,7 @@ ssmp2_mgc_trans_ack_msg(Mid, TransId) ->
%%
%% MG generator stuff
%%
-ssmp2_mg_event_sequence(text, tcp) ->
+ssmp2_mg_event_sequence(Factor, text, tcp) ->
Mid = {deviceName,"mg"},
RI = [
{port, 2944},
@@ -1187,7 +1181,8 @@ ssmp2_mg_event_sequence(text, tcp) ->
Tid1 = #megaco_term_id{id = ["00000000","00000000","00000001"]},
Tid2 = #megaco_term_id{id = ["00000000","00000000","00000002"]},
NotifyReqVerify = ssmp2_mg_verify_notify_request_fun(Tid1, Tid2),
- AckVerify = ssmp2_mg_verify_ack_fun(),
+ AckVerify = ssmp2_mg_verify_ack_fun(),
+ SECS = fun(T) -> ?SECS(Factor * T) end,
EvSeq = [
{debug, true},
{megaco_trace, disable},
@@ -1206,12 +1201,12 @@ ssmp2_mg_event_sequence(text, tcp) ->
{megaco_update_conn_info, protocol_version, ?VERSION},
{megaco_update_conn_info, segment_send, infinity},
{megaco_update_conn_info, max_pdu_size, 128},
- {sleep, 1000},
+ {sleep, ?SECS(1)},
{megaco_callback, handle_trans_request, NotifyReqVerify},
- {megaco_callback, handle_trans_ack, AckVerify, 5000},
+ {megaco_callback, handle_trans_ack, AckVerify, SECS(5)},
megaco_stop_user,
megaco_stop,
- {sleep, 1000}
+ {sleep, ?SECS(1)}
],
EvSeq.
@@ -1220,12 +1215,12 @@ ssmp2_mg_verify_handle_connect_fun() ->
fun(Ev) -> ssmp2_mg_verify_handle_connect(Ev) end.
ssmp2_mg_verify_handle_connect({handle_connect, CH, 1}) ->
- io:format("ssmp2_mg_verify_handle_connect -> ok"
- "~n CH: ~p~n", [CH]),
+ p("ssmp2_mg_verify_handle_connect -> ok"
+ "~n CH: ~p", [CH]),
{ok, CH, ok};
ssmp2_mg_verify_handle_connect(Else) ->
- io:format("ssmp2_mg_verify_handle_connect -> unknown"
- "~n Else: ~p~n", [Else]),
+ p("ssmp2_mg_verify_handle_connect -> unknown"
+ "~n Else: ~p", [Else]),
{error, Else, ok}.
@@ -1235,14 +1230,13 @@ ssmp2_mg_verify_service_change_reply_fun() ->
ssmp2_mg_verify_scr({handle_trans_reply, _CH, 1, {ok, [AR]}, _}) ->
(catch ssmp2_mg_do_verify_scr(AR));
ssmp2_mg_verify_scr(Crap) ->
- io:format("ssmp2_mg_verify_scr -> error: "
- "~n Crap: ~p"
- "~n", [Crap]),
+ p("ssmp2_mg_verify_scr -> error: "
+ "~n Crap: ~p", [Crap]),
{error, Crap, ok}.
ssmp2_mg_do_verify_scr(AR) ->
- io:format("ssmp2_mg_do_verify_scr -> ok: "
- "~n AR: ~p~n", [AR]),
+ p("ssmp2_mg_do_verify_scr -> ok: "
+ "~n AR: ~p", [AR]),
CR =
case AR of
#'ActionReply'{commandReply = [CmdRep]} ->
@@ -1304,30 +1298,27 @@ ssmp2_mg_verify_notify_request(
{handle_trans_request, CH, V, ARs}, _Tid1, _Tid2) ->
{error, {invalid_trans_request, {CH, V, ARs}}, ok};
ssmp2_mg_verify_notify_request(Crap, _Tid1, _Tid2) ->
- io:format("ssmp2_mg_verify_notify_request -> unknown request"
- "~n Tid1: ~p"
- "~n Tid2: ~p"
- "~n Crap: ~p"
- "~n", [_Tid1, _Tid2, Crap]),
+ p("ssmp2_mg_verify_notify_request -> unknown request"
+ "~n Tid1: ~p"
+ "~n Tid2: ~p"
+ "~n Crap: ~p", [_Tid1, _Tid2, Crap]),
{error, {unexpected_event, Crap}, ok}.
ssmp2_mg_do_verify_notify_request(Tid1, Tid2, AR1, AR2) ->
- io:format("ssmp2_mg_do_verify_notify_request -> ok"
- "~n Tid1: ~p"
- "~n Tid2: ~p"
- "~n AR1: ~p"
- "~n AR2: ~p"
- "~n", [Tid1, Tid2, AR1, AR2]),
+ p("ssmp2_mg_do_verify_notify_request -> ok"
+ "~n Tid1: ~p"
+ "~n Tid2: ~p"
+ "~n AR1: ~p"
+ "~n AR2: ~p", [Tid1, Tid2, AR1, AR2]),
ActionReply1 = ssmp2_mg_do_verify_notify_request(Tid1, AR1),
ActionReply2 = ssmp2_mg_do_verify_notify_request(Tid2, AR2),
Reply = {{handle_ack, ssmp2}, [ActionReply1, ActionReply2]},
{ok, [AR1, AR2], Reply}.
ssmp2_mg_do_verify_notify_request(Tid, AR) ->
- io:format("ssmp2_mg_do_verify_notify_request -> ok"
- "~n Tid: ~p"
- "~n AR: ~p"
- "~n", [Tid, AR]),
+ p("ssmp2_mg_do_verify_notify_request -> ok"
+ "~n Tid: ~p"
+ "~n AR: ~p", [Tid, AR]),
{Cid, CR} =
case AR of
#'ActionRequest'{contextId = CtxId,
@@ -1375,9 +1366,8 @@ ssmp2_mg_verify_ack_fun() ->
fun(Event) -> ssmp2_mg_verify_ack(Event) end.
ssmp2_mg_verify_ack({handle_trans_ack, CH, ?VERSION, ok, ssmp2}) ->
- io:format("ssmp2_mg_verify_ack -> ok"
- "~n CH: ~p"
- "~n", [CH]),
+ p("ssmp2_mg_verify_ack -> ok"
+ "~n CH: ~p", [CH]),
{ok, CH, ok};
ssmp2_mg_verify_ack({handle_trans_ack, CH, ?VERSION, ok, CrapAckData}) ->
{error, {unknown_ack_data, CrapAckData, CH}, ok};
@@ -1412,6 +1402,8 @@ send_segmented_msg_plain3(doc) ->
"Third plain test that it is possible to send segmented messages. "
"Send window = 1. ";
send_segmented_msg_plain3(Config) when is_list(Config) ->
+ Factor = ?config(megaco_factor, Config),
+ ct:timetrap(?MINS(1) + Factor * ?MINS(1)),
Pre = fun() ->
MgcNode = make_node_name(mgc),
MgNode = make_node_name(mg),
@@ -1612,9 +1604,8 @@ ssmp3_mgc_verify_service_change_req_msg_fun() ->
end.
ssmp3_mgc_verify_service_change_req(#'MegacoMessage'{mess = Mess} = M) ->
- io:format("ssmp3_mgc_verify_service_change_req -> entry with"
- "~n M: ~p"
- "~n", [M]),
+ p("ssmp3_mgc_verify_service_change_req -> entry with"
+ "~n M: ~p", [M]),
Body =
case Mess of
#'Message'{version = 1,
@@ -1704,14 +1695,13 @@ ssmp3_mgc_verify_notify_reply_segment_msg_fun(SN, Last,
ssmp3_mgc_verify_notify_reply_segment(#'MegacoMessage'{mess = Mess} = M,
SN, Last, TransId, TermId, Cid) ->
- io:format("ssmp3_mgc_verify_notify_reply_segment -> entry with"
- "~n M: ~p"
- "~n SN: ~p"
- "~n Last: ~p"
- "~n TransId: ~p"
- "~n TermId: ~p"
- "~n Cid: ~p"
- "~n", [M, SN, Last, TransId, TermId, Cid]),
+ p("ssmp3_mgc_verify_notify_reply_segment -> entry with"
+ "~n M: ~p"
+ "~n SN: ~p"
+ "~n Last: ~p"
+ "~n TransId: ~p"
+ "~n TermId: ~p"
+ "~n Cid: ~p", [M, SN, Last, TransId, TermId, Cid]),
Body =
case Mess of
#'Message'{version = ?VERSION,
@@ -1903,12 +1893,12 @@ ssmp3_mg_verify_handle_connect_fun() ->
fun(Ev) -> ssmp3_mg_verify_handle_connect(Ev) end.
ssmp3_mg_verify_handle_connect({handle_connect, CH, 1}) ->
- io:format("ssmp3_mg_verify_handle_connect -> ok"
- "~n CH: ~p~n", [CH]),
+ p("ssmp3_mg_verify_handle_connect -> ok"
+ "~n CH: ~p", [CH]),
{ok, CH, ok};
ssmp3_mg_verify_handle_connect(Else) ->
- io:format("ssmp3_mg_verify_handle_connect -> unknown"
- "~n Else: ~p~n", [Else]),
+ p("ssmp3_mg_verify_handle_connect -> unknown"
+ "~n Else: ~p", [Else]),
{error, Else, ok}.
@@ -1918,14 +1908,13 @@ ssmp3_mg_verify_service_change_reply_fun() ->
ssmp3_mg_verify_scr({handle_trans_reply, _CH, 1, {ok, [AR]}, _}) ->
(catch ssmp3_mg_do_verify_scr(AR));
ssmp3_mg_verify_scr(Crap) ->
- io:format("ssmp3_mg_verify_scr -> error: "
- "~n Crap: ~p"
- "~n", [Crap]),
+ p("ssmp3_mg_verify_scr -> error: "
+ "~n Crap: ~p", [Crap]),
{error, Crap, ok}.
ssmp3_mg_do_verify_scr(AR) ->
- io:format("ssmp3_mg_do_verify_scr -> ok: "
- "~n AR: ~p~n", [AR]),
+ p("ssmp3_mg_do_verify_scr -> ok: "
+ "~n AR: ~p", [AR]),
CR =
case AR of
#'ActionReply'{commandReply = [CmdRep]} ->
@@ -1988,21 +1977,18 @@ ssmp3_mg_verify_notify_request(
{handle_trans_request, CH, V, ARs}, _Tids) ->
{error, {invalid_trans_request, {CH, V, ARs}}, ok};
ssmp3_mg_verify_notify_request(Crap, _Tids) ->
- io:format("ssmp3_mg_verify_notify_request -> unknown request"
- "~n Crap: ~p"
- "~n Tids: ~p"
- "~n", [Crap, _Tids]),
+ p("ssmp3_mg_verify_notify_request -> unknown request"
+ "~n Crap: ~p"
+ "~n Tids: ~p", [Crap, _Tids]),
{error, {unexpected_event, Crap}, ok}.
ssmp3_mg_do_verify_notify_request(Tids, ARs) ->
- io:format("ssmp3_mg_do_verify_notify_request -> ok"
- "~n Tids: ~p"
- "~n ARs: ~p"
- "~n", [Tids, ARs]),
+ p("ssmp3_mg_do_verify_notify_request -> ok"
+ "~n Tids: ~p"
+ "~n ARs: ~p", [Tids, ARs]),
ActionReplies = ssmp3_mg_do_verify_notify_request_ars(Tids, ARs),
- io:format("ssmp3_mg_do_verify_notify_request -> ok"
- "~n ActionReplies: ~p"
- "~n", [ActionReplies]),
+ p("ssmp3_mg_do_verify_notify_request -> ok"
+ "~n ActionReplies: ~p", [ActionReplies]),
Reply = {{handle_ack, ssmp3}, ActionReplies},
{ok, ARs, Reply}.
@@ -2016,10 +2002,9 @@ ssmp3_mg_do_verify_notify_request_ars([Tid|Tids], [AR|ARs], Acc) ->
ssmp3_mg_do_verify_notify_request_ars(Tids, ARs, [ActionReply|Acc]).
ssmp3_mg_do_verify_notify_request_ar(Tid, AR) ->
- io:format("ssmp3_mg_do_verify_notify_request_ar -> ok"
- "~n Tid: ~p"
- "~n AR: ~p"
- "~n", [Tid, AR]),
+ p("ssmp3_mg_do_verify_notify_request_ar -> ok"
+ "~n Tid: ~p"
+ "~n AR: ~p", [Tid, AR]),
{Cid, CR} =
case AR of
#'ActionRequest'{contextId = CtxId,
@@ -2067,9 +2052,8 @@ ssmp3_mg_verify_ack_fun() ->
fun(Event) -> ssmp3_mg_verify_ack(Event) end.
ssmp3_mg_verify_ack({handle_trans_ack, CH, ?VERSION, ok, ssmp3}) ->
- io:format("ssmp3_mg_verify_ack -> ok"
- "~n CH: ~p"
- "~n", [CH]),
+ p("ssmp3_mg_verify_ack -> ok"
+ "~n CH: ~p", [CH]),
{ok, CH, ok};
ssmp3_mg_verify_ack({handle_trans_ack, CH, ?VERSION, ok, CrapAckData}) ->
{error, {unknown_ack_data, CrapAckData, CH}, ok};
@@ -2105,7 +2089,7 @@ send_segmented_msg_plain4(doc) ->
"Send window = 3. ";
send_segmented_msg_plain4(Config) when is_list(Config) ->
Factor = ?config(megaco_factor, Config),
- ct:timetrap(Factor * ?SECS(60)),
+ ct:timetrap(Factor * ?MINS(1)),
Pre = fun() ->
MgcNode = make_node_name(mgc),
MgNode = make_node_name(mg),
@@ -2302,9 +2286,8 @@ ssmp4_mgc_verify_service_change_req_msg_fun() ->
end.
ssmp4_mgc_verify_service_change_req(#'MegacoMessage'{mess = Mess} = M) ->
- io:format("ssmp4_mgc_verify_service_change_req -> entry with"
- "~n M: ~p"
- "~n", [M]),
+ p("ssmp4_mgc_verify_service_change_req -> entry with"
+ "~n M: ~p", [M]),
Body =
case Mess of
#'Message'{version = 1,
@@ -2394,14 +2377,13 @@ ssmp4_mgc_verify_notify_reply_segment_msg_fun(SN, Last,
ssmp4_mgc_verify_notify_reply_segment(#'MegacoMessage'{mess = Mess} = M,
SN, Last, TransId, TermId, Cid) ->
- io:format("ssmp4_mgc_verify_notify_reply_segment -> entry with"
- "~n M: ~p"
- "~n SN: ~p"
- "~n Last: ~p"
- "~n TransId: ~p"
- "~n TermId: ~p"
- "~n Cid: ~p"
- "~n", [M, SN, Last, TransId, TermId, Cid]),
+ p("ssmp4_mgc_verify_notify_reply_segment -> entry with"
+ "~n M: ~p"
+ "~n SN: ~p"
+ "~n Last: ~p"
+ "~n TransId: ~p"
+ "~n TermId: ~p"
+ "~n Cid: ~p", [M, SN, Last, TransId, TermId, Cid]),
Body =
case Mess of
#'Message'{version = ?VERSION,
@@ -2582,7 +2564,7 @@ ssmp4_mg_event_sequence(Factor, text, tcp) ->
{megaco_update_conn_info, max_pdu_size, 128},
{sleep, 1000},
{megaco_callback, handle_trans_request, NotifyReqVerify},
- {megaco_callback, handle_trans_ack, AckVerify, TO(15000)},
+ {megaco_callback, handle_trans_ack, AckVerify, TO(?SECS(15))},
megaco_stop_user,
megaco_stop,
{sleep, 1000}
@@ -2594,12 +2576,12 @@ ssmp4_mg_verify_handle_connect_fun() ->
fun(Ev) -> ssmp4_mg_verify_handle_connect(Ev) end.
ssmp4_mg_verify_handle_connect({handle_connect, CH, 1}) ->
- io:format("ssmp4_mg_verify_handle_connect -> ok"
- "~n CH: ~p~n", [CH]),
+ p("ssmp4_mg_verify_handle_connect -> ok"
+ "~n CH: ~p", [CH]),
{ok, CH, ok};
ssmp4_mg_verify_handle_connect(Else) ->
- io:format("ssmp4_mg_verify_handle_connect -> unknown"
- "~n Else: ~p~n", [Else]),
+ p("ssmp4_mg_verify_handle_connect -> unknown"
+ "~n Else: ~p", [Else]),
{error, Else, ok}.
@@ -2609,14 +2591,13 @@ ssmp4_mg_verify_service_change_reply_fun() ->
ssmp4_mg_verify_scr({handle_trans_reply, _CH, 1, {ok, [AR]}, _}) ->
(catch ssmp4_mg_do_verify_scr(AR));
ssmp4_mg_verify_scr(Crap) ->
- io:format("ssmp4_mg_verify_scr -> error: "
- "~n Crap: ~p"
- "~n", [Crap]),
+ p("ssmp4_mg_verify_scr -> error: "
+ "~n Crap: ~p", [Crap]),
{error, Crap, ok}.
ssmp4_mg_do_verify_scr(AR) ->
- io:format("ssmp4_mg_do_verify_scr -> ok: "
- "~n AR: ~p~n", [AR]),
+ p("ssmp4_mg_do_verify_scr -> ok: "
+ "~n AR: ~p", [AR]),
CR =
case AR of
#'ActionReply'{commandReply = [CmdRep]} ->
@@ -2761,9 +2742,8 @@ ssmp4_mg_verify_ack_fun() ->
fun(Event) -> ssmp4_mg_verify_ack(Event) end.
ssmp4_mg_verify_ack({handle_trans_ack, CH, ?VERSION, ok, ssmp4}) ->
- io:format("ssmp4_mg_verify_ack -> ok"
- "~n CH: ~p"
- "~n", [CH]),
+ p("ssmp4_mg_verify_ack -> ok"
+ "~n CH: ~p", [CH]),
{ok, CH, ok};
ssmp4_mg_verify_ack({handle_trans_ack, CH, ?VERSION, ok, CrapAckData}) ->
{error, {unknown_ack_data, CrapAckData, CH}, ok};
@@ -2992,9 +2972,8 @@ ssmo1_mgc_verify_service_change_req_msg_fun() ->
end.
ssmo1_mgc_verify_service_change_req(#'MegacoMessage'{mess = Mess} = M) ->
- io:format("ssmo1_mgc_verify_service_change_req -> entry with"
- "~n M: ~p"
- "~n", [M]),
+ p("ssmo1_mgc_verify_service_change_req -> entry with"
+ "~n M: ~p", [M]),
Body =
case Mess of
#'Message'{version = 1,
@@ -7896,12 +7875,12 @@ await_completion(Ids) ->
ok;
{error, {OK, ERROR}} ->
d("ERROR => "
- "~n OK: ~p"
- "~n ERROR: ~p", [OK, ERROR]),
+ "~n OK: ~p"
+ "~n ERROR: ~p", [OK, ERROR]),
?ERROR({failed, ERROR});
{error, Reply} ->
d("ERROR => "
- "~n Reply: ~p", [Reply]),
+ "~n Reply: ~p", [Reply]),
?ERROR({failed, Reply})
end.
diff --git a/lib/megaco/test/megaco_test_lib.erl b/lib/megaco/test/megaco_test_lib.erl
index d73ed45add..97d408bee8 100644
--- a/lib/megaco/test/megaco_test_lib.erl
+++ b/lib/megaco/test/megaco_test_lib.erl
@@ -29,6 +29,7 @@
%% -compile(export_all).
-export([
+ proxy_call/3,
log/4,
error/3,
@@ -71,6 +72,31 @@
%% ----------------------------------------------------------------
+%% Proxy Call
+%% This is used when we need to assign a timeout to a call, but the
+%% call itself does not provide such an argument.
+%%
+%% This has nothing to to with the proxy_start and proxy_init
+%% functions below.
+
+proxy_call(F, Timeout, Default)
+ when is_function(F, 0) andalso
+ is_integer(Timeout) andalso (Timeout > 0) andalso
+ is_function(Default, 0) ->
+ {P, M} = erlang:spawn_monitor(fun() -> exit(F()) end),
+ receive
+ {'DOWN', M, process, P, Reply} ->
+ Reply
+ after Timeout ->
+ erlang:demonitor(M, [flush]),
+ exit(P, kill),
+ Default()
+ end;
+proxy_call(F, Timeout, Default) ->
+ proxy_call(F, Timeout, fun() -> Default end).
+
+
+%% ----------------------------------------------------------------
%% Time related function
%%
@@ -164,6 +190,10 @@ os_base_skip(Skippable, OsFam, OsName) ->
case lists:keysearch(OsFam, 1, Skippable) of
{value, {OsFam, OsName}} ->
true;
+ {value, {OsFam, Check}} when is_function(Check, 0) ->
+ Check();
+ {value, {OsFam, Check}} when is_function(Check, 1) ->
+ Check(os:version());
{value, {OsFam, OsNames}} when is_list(OsNames) ->
%% OsNames is a list of:
%% [atom()|{atom(), function/0 | function/1}]
@@ -443,11 +473,31 @@ pprint(F, A) ->
init_per_suite(Config) ->
+ ct:timetrap(minutes(3)),
+
+ try analyze_and_print_host_info() of
+ {Factor, HostInfo} when is_integer(Factor) ->
+ try maybe_skip(HostInfo) of
+ true ->
+ {skip, "Unstable host and/or os (or combo thererof)"};
+ false ->
+ maybe_start_global_sys_monitor(Config),
+ [{megaco_factor, Factor} | Config]
+ catch
+ throw:{skip, _} = SKIP ->
+ SKIP
+ end
+ catch
+ throw:{skip, _} = SKIP ->
+ SKIP
+ end.
+
+maybe_skip(HostInfo) ->
+
%% We have some crap machines that causes random test case failures
%% for no obvious reason. So, attempt to identify those without actually
%% checking for the host name...
- %% We have two "machines" we are checking for. Both are old installations
- %% running on really slow VMs (the host machines are old and tired).
+
LinuxVersionVerify =
fun(V) when (V > {3,6,11}) ->
false; % OK - No skip
@@ -458,6 +508,28 @@ init_per_suite(Config) ->
_ ->
false
end;
+ (V) when (V =:= {3,4,20}) ->
+ case string:trim(os:cmd("cat /etc/issue")) of
+ "Wind River Linux 5.0.1.0" ++ _ -> % *Old* Wind River => skip
+ true;
+ _ ->
+ false
+ end;
+ (V) when (V =:= {2,6,32}) ->
+ case string:trim(os:cmd("cat /etc/issue")) of
+ "Debian GNU/Linux 6.0 " ++ _ -> % Stone age Debian => Skip
+ true;
+ _ ->
+ false
+ end;
+ (V) when (V =:= {2,6,10}) ->
+ case string:trim(os:cmd("cat /etc/issue")) of
+ "MontaVista" ++ _ -> % Stone age MontaVista => Skip
+ %% The real problem is that the machine is *very* slow
+ true;
+ _ ->
+ false
+ end;
(V) when (V > {2,6,24}) ->
false; % OK - No skip
(_) ->
@@ -478,37 +550,22 @@ init_per_suite(Config) ->
%% This version is *not* ok: Skip
true
end,
- %% We are "looking" for a specific machine (a VM)
- %% which are *old and crappy" and slow, because it
- %% causes a bunch of test cases to fail randomly.
- %% But we don not want to test for the host name...
- %% WinVersionVerify =
- %% fun(V) when (V =:= {6,2,9200}) ->
- %% try erlang:system_info(schedulers) of
- %% 2 ->
- %% true;
- %% _ ->
- %% false
- %% catch
- %% _:_:_ ->
- %% true
- %% end;
- %% (_) ->
- %% false
- %% end,
+ SkipWindowsOnVirtual =
+ fun() ->
+ SysMan = win_sys_info_lookup(system_manufacturer, HostInfo),
+ case string:to_lower(SysMan) of
+ "vmware" ++ _ ->
+ true;
+ _ ->
+ false
+ end
+ end,
COND = [
{unix, [{linux, LinuxVersionVerify},
- {darwin, DarwinVersionVerify}]}%% ,
- %% {win32, [{nt, WinVersionVerify}]}
+ {darwin, DarwinVersionVerify}]},
+ {win32, SkipWindowsOnVirtual}
],
- case os_based_skip(COND) of
- true ->
- {skip, "Unstable host and/or os (or combo thererof)"};
- false ->
- Factor = analyze_and_print_host_info(),
- maybe_start_global_sys_monitor(Config),
- [{megaco_factor, Factor} | Config]
- end.
+ os_based_skip(COND).
%% We start the global system monitor unless explicitly disabled
maybe_start_global_sys_monitor(Config) ->
@@ -569,10 +626,6 @@ end_per_testcase(_Case, Config) ->
%% the load for some test cases. Such as run time or number of
%% iteraions. This only works for some OSes.
%%
-%% We make some calculations on Linux, OpenBSD and FreeBSD.
-%% On SunOS we always set the factor to 2 (just to be on the safe side)
-%% On all other os:es (mostly windows) we check the number of schedulers,
-%% but at least the factor will be 2.
analyze_and_print_host_info() ->
{OsFam, OsName} = os:type(),
Version =
@@ -591,6 +644,8 @@ analyze_and_print_host_info() ->
analyze_and_print_freebsd_host_info(Version);
{unix, netbsd} ->
analyze_and_print_netbsd_host_info(Version);
+ {unix, darwin} ->
+ analyze_and_print_darwin_host_info(Version);
{unix, sunos} ->
analyze_and_print_solaris_host_info(Version);
{win32, nt} ->
@@ -601,19 +656,7 @@ analyze_and_print_host_info() ->
"~n Version: ~p"
"~n Num Schedulers: ~s"
"~n", [OsFam, OsName, Version, str_num_schedulers()]),
- try erlang:system_info(schedulers) of
- 1 ->
- 10;
- 2 ->
- 5;
- N when (N =< 6) ->
- 2;
- _ ->
- 1
- catch
- _:_:_ ->
- 10
- end
+ {num_schedulers_to_factor(), []}
end.
str_num_schedulers() ->
@@ -623,106 +666,285 @@ str_num_schedulers() ->
_:_:_ -> "-"
end.
+num_schedulers_to_factor() ->
+ try erlang:system_info(schedulers) of
+ 1 ->
+ 10;
+ 2 ->
+ 5;
+ N when (N =< 6) ->
+ 2;
+ _ ->
+ 1
+ catch
+ _:_:_ ->
+ 10
+ end.
-analyze_and_print_linux_host_info(Version) ->
+
+
+linux_which_distro(Version) ->
case file:read_file_info("/etc/issue") of
{ok, _} ->
- io:format("Linux: ~s"
- "~n ~s"
- "~n",
- [Version, string:trim(os:cmd("cat /etc/issue"))]);
+ case [string:trim(S) ||
+ S <- string:tokens(os:cmd("cat /etc/issue"), [$\n])] of
+ [DistroStr|_] ->
+ io:format("Linux: ~s"
+ "~n ~s"
+ "~n",
+ [Version, DistroStr]),
+ case DistroStr of
+ "Wind River Linux" ++ _ ->
+ wind_river;
+ "MontaVista" ++ _ ->
+ montavista;
+ "Yellow Dog" ++ _ ->
+ yellow_dog;
+ _ ->
+ other
+ end;
+ X ->
+ io:format("Linux: ~s"
+ "~n ~p"
+ "~n",
+ [Version, X]),
+ other
+ end;
_ ->
io:format("Linux: ~s"
- "~n", [Version])
- end,
+ "~n", [Version]),
+ other
+ end.
+
+
+analyze_and_print_linux_host_info(Version) ->
+ Distro = linux_which_distro(Version),
Factor =
- case (catch linux_which_cpuinfo()) of
+ case (catch linux_which_cpuinfo(Distro)) of
{ok, {CPU, BogoMIPS}} ->
io:format("CPU: "
"~n Model: ~s"
- "~n BogoMIPS: ~s"
+ "~n BogoMIPS: ~w"
"~n Num Schedulers: ~s"
"~n", [CPU, BogoMIPS, str_num_schedulers()]),
- %% We first assume its a float, and if not try integer
- try list_to_float(string:trim(BogoMIPS)) of
- F when F > 4000 ->
+ if
+ (BogoMIPS > 20000) ->
1;
- F when F > 1000 ->
+ (BogoMIPS > 10000) ->
2;
- F when F > 500 ->
+ (BogoMIPS > 5000) ->
3;
- _ ->
- 5
- catch
- _:_:_ ->
- try list_to_integer(string:trim(BogoMIPS)) of
- I when I > 4000 ->
- 1;
- I when I > 1000 ->
- 2;
- I when I > 500 ->
- 3;
- _ ->
- 5
- catch
- _:_:_ ->
- 5 % Be a "bit" conservative...
- end
+ (BogoMIPS > 2000) ->
+ 5;
+ (BogoMIPS > 1000) ->
+ 8;
+ true ->
+ 10
end;
{ok, CPU} ->
io:format("CPU: "
"~n Model: ~s"
"~n Num Schedulers: ~s"
"~n", [CPU, str_num_schedulers()]),
- 2; % Be a "bit" conservative...
+ num_schedulers_to_factor();
_ ->
- 5 % Be a "bit" (more) conservative...
+ 5
end,
%% Check if we need to adjust the factor because of the memory
try linux_which_meminfo() of
AddFactor ->
- Factor + AddFactor
+ {Factor + AddFactor, []}
catch
_:_:_ ->
- Factor
+ {Factor, []}
+ end.
+
+
+linux_cpuinfo_lookup(Key) when is_list(Key) ->
+ linux_info_lookup(Key, "/proc/cpuinfo").
+
+linux_cpuinfo_cpu() ->
+ case linux_cpuinfo_lookup("cpu") of
+ [Model] ->
+ Model;
+ _ ->
+ "-"
+ end.
+
+linux_cpuinfo_motherboard() ->
+ case linux_cpuinfo_lookup("motherboard") of
+ [MB] ->
+ MB;
+ _ ->
+ "-"
+ 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_which_cpuinfo() ->
+linux_cpuinfo_total_bogomips() ->
+ case linux_cpuinfo_lookup("total bogomips") of
+ [TBM] ->
+ try bogomips_to_int(TBM)
+ catch
+ _:_:_ ->
+ "-"
+ end;
+ _ ->
+ "-"
+ end.
+
+bogomips_to_int(BM) ->
+ try list_to_float(BM) of
+ F ->
+ floor(F)
+ catch
+ _:_:_ ->
+ try list_to_integer(BM) of
+ I ->
+ I
+ catch
+ _:_:_ ->
+ throw(noinfo)
+ end
+ end.
+
+linux_cpuinfo_model() ->
+ case linux_cpuinfo_lookup("model") of
+ [M] ->
+ M;
+ _ ->
+ "-"
+ end.
+
+linux_cpuinfo_platform() ->
+ case linux_cpuinfo_lookup("platform") of
+ [P] ->
+ P;
+ _ ->
+ "-"
+ end.
+
+linux_cpuinfo_model_name() ->
+ case linux_cpuinfo_lookup("model name") of
+ [P|_] ->
+ P;
+ _X ->
+ "-"
+ end.
+
+linux_cpuinfo_processor() ->
+ case linux_cpuinfo_lookup("Processor") of
+ [P] ->
+ P;
+ _ ->
+ "-"
+ end.
+
+linux_which_cpuinfo(montavista) ->
+ CPU =
+ case linux_cpuinfo_cpu() of
+ "-" ->
+ throw(noinfo);
+ Model ->
+ case linux_cpuinfo_motherboard() of
+ "-" ->
+ Model;
+ MB ->
+ Model ++ " (" ++ MB ++ ")"
+ end
+ end,
+ case linux_cpuinfo_bogomips() of
+ "-" ->
+ {ok, CPU};
+ BMips ->
+ {ok, {CPU, BMips}}
+ end;
+
+linux_which_cpuinfo(yellow_dog) ->
+ CPU =
+ case linux_cpuinfo_cpu() of
+ "-" ->
+ throw(noinfo);
+ Model ->
+ case linux_cpuinfo_motherboard() of
+ "-" ->
+ Model;
+ MB ->
+ Model ++ " (" ++ MB ++ ")"
+ end
+ end,
+ {ok, CPU};
+
+linux_which_cpuinfo(wind_river) ->
+ CPU =
+ case linux_cpuinfo_model() of
+ "-" ->
+ throw(noinfo);
+ Model ->
+ case linux_cpuinfo_platform() of
+ "-" ->
+ Model;
+ Platform ->
+ Model ++ " (" ++ Platform ++ ")"
+ end
+ end,
+ case linux_cpuinfo_total_bogomips() of
+ "-" ->
+ {ok, CPU};
+ BMips ->
+ {ok, {CPU, BMips}}
+ end;
+
+linux_which_cpuinfo(other) ->
%% Check for x86 (Intel or AMD)
CPU =
- try [string:trim(S) || S <- string:tokens(os:cmd("grep \"model name\" /proc/cpuinfo"), [$:,$\n])] of
- ["model name", ModelName | _] ->
- ModelName;
- _ ->
+ case linux_cpuinfo_model_name() of
+ "-" ->
%% ARM (at least some distros...)
- try [string:trim(S) || S <- string:tokens(os:cmd("grep \"Processor\" /proc/cpuinfo"), [$:,$\n])] of
- ["Processor", Proc | _] ->
- Proc;
- _ ->
+ case linux_cpuinfo_processor() of
+ "-" ->
%% Ok, we give up
- throw(noinfo)
- catch
- _:_:_ ->
- throw(noinfo)
- end
- catch
- _:_:_ ->
- throw(noinfo)
+ throw(noinfo);
+ Proc ->
+ Proc
+ end;
+ ModelName ->
+ ModelName
end,
- try [string:trim(S) || S <- string:tokens(os:cmd("grep -i \"bogomips\" /proc/cpuinfo"), [$:,$\n])] of
- [_, BMips | _] ->
- {ok, {CPU, BMips}};
+ case linux_cpuinfo_bogomips() of
+ "-" ->
+ {ok, CPU};
+ BMips ->
+ {ok, {CPU, BMips}}
+ end.
+
+linux_meminfo_lookup(Key) when is_list(Key) ->
+ linux_info_lookup(Key, "/proc/meminfo").
+
+linux_meminfo_memtotal() ->
+ case linux_meminfo_lookup("MemTotal") of
+ [X] ->
+ X;
_ ->
- {ok, CPU}
- catch
- _:_:_ ->
- {ok, CPU}
+ "-"
end.
%% We *add* the value this return to the Factor.
linux_which_meminfo() ->
- try [string:trim(S) || S <- string:tokens(os:cmd("grep MemTotal /proc/meminfo"), [$:])] of
- [_, MemTotal] ->
+ case linux_meminfo_memtotal() of
+ "-" ->
+ 0;
+ MemTotal ->
io:format("Memory:"
"~n ~s"
"~n", [MemTotal]),
@@ -752,12 +974,7 @@ linux_which_meminfo() ->
end;
_X ->
0
- end;
- _ ->
- 0
- catch
- _:_:_ ->
- 0
+ end
end.
@@ -843,11 +1060,11 @@ analyze_and_print_openbsd_host_info(Version) ->
true ->
3
end,
- CPUFactor + MemAddFactor
+ {CPUFactor + MemAddFactor, []}
end
catch
_:_:_ ->
- 1
+ {5, []}
end.
@@ -930,21 +1147,22 @@ analyze_and_print_freebsd_host_info(Version) ->
true ->
3
end,
- CPUFactor + MemAddFactor
+ {CPUFactor + MemAddFactor, []}
end
catch
_:_:_ ->
io:format("CPU:"
"~n Num Schedulers: ~w"
"~n", [erlang:system_info(schedulers)]),
- case erlang:system_info(schedulers) of
- 1 ->
- 10;
- 2 ->
- 5;
- _ ->
- 2
- end
+ Factor = case erlang:system_info(schedulers) of
+ 1 ->
+ 10;
+ 2 ->
+ 5;
+ _ ->
+ 2
+ end,
+ {Factor, []}
end.
analyze_freebsd_cpu(Extract) ->
@@ -1067,21 +1285,22 @@ analyze_and_print_netbsd_host_info(Version) ->
true ->
3
end,
- CPUFactor + MemAddFactor
+ {CPUFactor + MemAddFactor, []}
end
catch
_:_:_ ->
io:format("CPU:"
"~n Num Schedulers: ~w"
"~n", [erlang:system_info(schedulers)]),
- case erlang:system_info(schedulers) of
- 1 ->
- 10;
- 2 ->
- 5;
- _ ->
- 2
- end
+ Factor = case erlang:system_info(schedulers) of
+ 1 ->
+ 10;
+ 2 ->
+ 5;
+ _ ->
+ 2
+ end,
+ {Factor, []}
end.
analyze_netbsd_cpu(Extract) ->
@@ -1120,6 +1339,289 @@ analyze_netbsd_item(Extract, Key, Process, Default) ->
+%% Model Identifier: Macmini7,1
+%% Processor Name: Intel Core i5
+%% Processor Speed: 2,6 GHz
+%% Number of Processors: 1
+%% Total Number of Cores: 2
+%% L2 Cache (per Core): 256 KB
+%% L3 Cache: 3 MB
+%% Hyper-Threading Technology: Enabled
+%% Memory: 16 GB
+
+analyze_and_print_darwin_host_info(Version) ->
+ %% This stuff is for macOS.
+ %% If we ever tested on a pure darwin machine,
+ %% we need to find some other way to find some info...
+ %% Also, I suppose its possible that we for some other
+ %% reason *fail* to get the info...
+ case analyze_darwin_software_info() of
+ [] ->
+ io:format("Darwin:"
+ "~n Version: ~s"
+ "~n Num Schedulers: ~s"
+ "~n", [Version, str_num_schedulers()]),
+ {num_schedulers_to_factor(), []};
+ SwInfo when is_list(SwInfo) ->
+ SystemVersion = analyze_darwin_sw_system_version(SwInfo),
+ KernelVersion = analyze_darwin_sw_kernel_version(SwInfo),
+ HwInfo = analyze_darwin_hardware_info(),
+ ModelName = analyze_darwin_hw_model_name(HwInfo),
+ ModelId = analyze_darwin_hw_model_identifier(HwInfo),
+ ProcName = analyze_darwin_hw_processor_name(HwInfo),
+ ProcSpeed = analyze_darwin_hw_processor_speed(HwInfo),
+ NumProc = analyze_darwin_hw_number_of_processors(HwInfo),
+ NumCores = analyze_darwin_hw_total_number_of_cores(HwInfo),
+ Memory = analyze_darwin_hw_memory(HwInfo),
+ io:format("Darwin:"
+ "~n System Version: ~s"
+ "~n Kernel Version: ~s"
+ "~n Model: ~s (~s)"
+ "~n Processor: ~s (~s, ~s, ~s)"
+ "~n Memory: ~s"
+ "~n Num Schedulers: ~s"
+ "~n", [SystemVersion, KernelVersion,
+ ModelName, ModelId,
+ ProcName, ProcSpeed, NumProc, NumCores,
+ Memory,
+ str_num_schedulers()]),
+ CPUFactor = analyze_darwin_cpu_to_factor(ProcName,
+ ProcSpeed,
+ NumProc,
+ NumCores),
+ MemFactor = analyze_darwin_memory_to_factor(Memory),
+ if (MemFactor =:= 1) ->
+ {CPUFactor, []};
+ true ->
+ {CPUFactor + MemFactor, []}
+ end
+ end.
+
+analyze_darwin_sw_system_version(SwInfo) ->
+ proplists:get_value("system version", SwInfo, "-").
+
+analyze_darwin_sw_kernel_version(SwInfo) ->
+ proplists:get_value("kernel version", SwInfo, "-").
+
+analyze_darwin_software_info() ->
+ analyze_darwin_system_profiler("SPSoftwareDataType").
+
+analyze_darwin_hw_model_name(HwInfo) ->
+ proplists:get_value("model name", HwInfo, "-").
+
+analyze_darwin_hw_model_identifier(HwInfo) ->
+ proplists:get_value("model identifier", HwInfo, "-").
+
+analyze_darwin_hw_processor_name(HwInfo) ->
+ proplists:get_value("processor name", HwInfo, "-").
+
+analyze_darwin_hw_processor_speed(HwInfo) ->
+ proplists:get_value("processor speed", HwInfo, "-").
+
+analyze_darwin_hw_number_of_processors(HwInfo) ->
+ proplists:get_value("number of processors", HwInfo, "-").
+
+analyze_darwin_hw_total_number_of_cores(HwInfo) ->
+ proplists:get_value("total number of cores", HwInfo, "-").
+
+analyze_darwin_hw_memory(HwInfo) ->
+ proplists:get_value("memory", HwInfo, "-").
+
+analyze_darwin_hardware_info() ->
+ analyze_darwin_system_profiler("SPHardwareDataType").
+
+%% This basically has the structure: "Key: Value"
+%% But could also be (for example):
+%% "Something:" (which we ignore)
+%% "Key: Value1:Value2"
+analyze_darwin_system_profiler(DataType) ->
+ %% First, make sure the program actually exist:
+ case os:cmd("which system_profiler") of
+ [] ->
+ [];
+ _ ->
+ D0 = os:cmd("system_profiler " ++ DataType),
+ D1 = string:tokens(D0, [$\n]),
+ D2 = [string:trim(S1) || S1 <- D1],
+ D3 = [string:tokens(S2, [$:]) || S2 <- D2],
+ analyze_darwin_system_profiler2(D3)
+ end.
+
+analyze_darwin_system_profiler2(L) ->
+ analyze_darwin_system_profiler2(L, []).
+
+analyze_darwin_system_profiler2([], Acc) ->
+ [{string:to_lower(K), V} || {K, V} <- lists:reverse(Acc)];
+analyze_darwin_system_profiler2([[_]|T], Acc) ->
+ analyze_darwin_system_profiler2(T, Acc);
+analyze_darwin_system_profiler2([[H1,H2]|T], Acc) ->
+ analyze_darwin_system_profiler2(T, [{H1, string:trim(H2)}|Acc]);
+analyze_darwin_system_profiler2([[H|TH0]|T], Acc) ->
+ %% Some value parts has ':' in them, so put them together
+ TH1 = colonize(TH0),
+ analyze_darwin_system_profiler2(T, [{H, string:trim(TH1)}|Acc]).
+
+%% This is only called if the length is at least 2
+colonize([L1, L2]) ->
+ L1 ++ ":" ++ L2;
+colonize([H|T]) ->
+ H ++ ":" ++ colonize(T).
+
+
+%% The memory looks like this "<size> <unit>". Example: "2 GB"
+analyze_darwin_memory_to_factor(Mem) ->
+ case [string:to_lower(S) || S <- string:tokens(Mem, [$\ ])] of
+ [_SzStr, "tb"] ->
+ 1;
+ [SzStr, "gb"] ->
+ try list_to_integer(SzStr) of
+ Sz when Sz < 2 ->
+ 20;
+ Sz when Sz < 4 ->
+ 10;
+ Sz when Sz < 8 ->
+ 5;
+ Sz when Sz < 16 ->
+ 2;
+ _ ->
+ 1
+ catch
+ _:_:_ ->
+ 20
+ end;
+ [_SzStr, "mb"] ->
+ 20;
+ _ ->
+ 20
+ end.
+
+
+%% The speed is a string: "<speed> <unit>"
+%% the speed may be a float, which we transforms into an integer of MHz.
+%% To calculate a factor based on processor speed, number of procs
+%% and number of cores is ... not an exact ... science ...
+analyze_darwin_cpu_to_factor(_ProcName,
+ ProcSpeedStr, NumProcStr, NumCoresStr) ->
+ Speed =
+ case [string:to_lower(S) || S <- string:tokens(ProcSpeedStr, [$\ ])] of
+ [SpeedStr, "mhz"] ->
+ try list_to_integer(SpeedStr) of
+ SpeedI ->
+ SpeedI
+ catch
+ _:_:_ ->
+ try list_to_float(SpeedStr) of
+ SpeedF ->
+ trunc(SpeedF)
+ catch
+ _:_:_ ->
+ -1
+ end
+ end;
+ [SpeedStr, "ghz"] ->
+ try list_to_float(SpeedStr) of
+ SpeedF ->
+ trunc(1000*SpeedF)
+ catch
+ _:_:_ ->
+ try list_to_integer(SpeedStr) of
+ SpeedI ->
+ 1000*SpeedI
+ catch
+ _:_:_ ->
+ -1
+ end
+ end;
+ _ ->
+ -1
+ end,
+ NumProc = try list_to_integer(NumProcStr) of
+ NumProcI ->
+ NumProcI
+ catch
+ _:_:_ ->
+ 1
+ end,
+ NumCores = try list_to_integer(NumCoresStr) of
+ NumCoresI ->
+ NumCoresI
+ catch
+ _:_:_ ->
+ 1
+ end,
+ if
+ (Speed > 3000) ->
+ if
+ (NumProc =:= 1) ->
+ if
+ (NumCores < 2) ->
+ 5;
+ (NumCores < 4) ->
+ 3;
+ (NumCores < 6) ->
+ 2;
+ true ->
+ 1
+ end;
+ true ->
+ if
+ (NumCores < 4) ->
+ 2;
+ true ->
+ 1
+ end
+ end;
+ (Speed > 2000) ->
+ if
+ (NumProc =:= 1) ->
+ if
+ (NumCores < 2) ->
+ 8;
+ (NumCores < 4) ->
+ 5;
+ (NumCores < 6) ->
+ 3;
+ true ->
+ 1
+ end;
+ true ->
+ if
+ (NumCores < 4) ->
+ 5;
+ (NumCores < 8) ->
+ 2;
+ true ->
+ 1
+ end
+ end;
+ true ->
+ if
+ (NumProc =:= 1) ->
+ if
+ (NumCores < 2) ->
+ 10;
+ (NumCores < 4) ->
+ 7;
+ (NumCores < 6) ->
+ 5;
+ (NumCores < 8) ->
+ 3;
+ true ->
+ 1
+ end;
+ true ->
+ if
+ (NumCores < 4) ->
+ 8;
+ (NumCores < 8) ->
+ 4;
+ true ->
+ 1
+ end
+ end
+ end.
+
+
analyze_and_print_solaris_host_info(Version) ->
Release =
case file:read_file_info("/etc/release") of
@@ -1231,19 +1733,19 @@ analyze_and_print_solaris_host_info(Version) ->
_:_:_ ->
10
end,
- try erlang:system_info(schedulers) of
- 1 ->
- 10;
- 2 ->
- 5;
- N when (N =< 6) ->
- 2;
- _ ->
- 1
- catch
- _:_:_ ->
- 10
- end + MemFactor.
+ {try erlang:system_info(schedulers) of
+ 1 ->
+ 10;
+ 2 ->
+ 5;
+ N when (N =< 6) ->
+ 2;
+ _ ->
+ 1
+ catch
+ _:_:_ ->
+ 10
+ end + MemFactor, []}.
analyze_and_print_win_host_info(Version) ->
@@ -1315,7 +1817,7 @@ analyze_and_print_win_host_info(Version) ->
_ ->
2
end,
- CPUFactor + MemFactor.
+ {CPUFactor + MemFactor, SysInfo}.
win_sys_info_lookup(Key, SysInfo) ->
win_sys_info_lookup(Key, SysInfo, "-").
@@ -1330,14 +1832,25 @@ win_sys_info_lookup(Key, SysInfo, Def) ->
%% This function only extracts the prop we actually care about!
which_win_system_info() ->
- SysInfo = os:cmd("systeminfo"),
- try process_win_system_info(string:tokens(SysInfo, [$\r, $\n]), [])
- catch
- _:_:_ ->
- io:format("Failed process System info: "
- "~s~n", [SysInfo]),
- []
- end.
+ F = fun() ->
+ try
+ begin
+ SysInfo = os:cmd("systeminfo"),
+ process_win_system_info(
+ string:tokens(SysInfo, [$\r, $\n]), [])
+ end
+ catch
+ C:E:S ->
+ io:format("Failed get or process System info: "
+ " Error Class: ~p"
+ " Error: ~p"
+ " Stack: ~p"
+ "~n", [C, E, S]),
+ []
+ end
+ end,
+ proxy_call(F, minutes(1),
+ fun() -> throw({skip, "System info timeout"}) end).
process_win_system_info([], Acc) ->
Acc;
@@ -1373,6 +1886,22 @@ process_win_system_info([H|T], Acc) ->
end.
+linux_info_lookup(Key, File) ->
+ try [string:trim(S) || S <- string:tokens(os:cmd("grep " ++ "\"" ++ Key ++ "\"" ++ " " ++ File), [$:,$\n])] of
+ Info ->
+ linux_info_lookup_collect(Key, Info, [])
+ catch
+ _:_:_ ->
+ "-"
+ end.
+
+linux_info_lookup_collect(_Key, [], Values) ->
+ lists:reverse(Values);
+linux_info_lookup_collect(Key, [Key, Value|Rest], Values) ->
+ linux_info_lookup_collect(Key, Rest, [Value|Values]);
+linux_info_lookup_collect(_, _, Values) ->
+ lists:reverse(Values).
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Set kill timer
diff --git a/lib/megaco/test/megaco_test_lib.hrl b/lib/megaco/test/megaco_test_lib.hrl
index f6af199d4e..0777adbcd0 100644
--- a/lib/megaco/test/megaco_test_lib.hrl
+++ b/lib/megaco/test/megaco_test_lib.hrl
@@ -29,6 +29,8 @@
-define(LIB, megaco_test_lib).
+-define(PCALL(F, T, D), ?LIB:proxy_call(F, T, D)).
+
-define(APPLY(Proxy, Fun),
Proxy ! {apply, Fun}).
diff --git a/lib/megaco/test/megaco_udp_SUITE.erl b/lib/megaco/test/megaco_udp_SUITE.erl
index 05910e50a9..42d0060fcf 100644
--- a/lib/megaco/test/megaco_udp_SUITE.erl
+++ b/lib/megaco/test/megaco_udp_SUITE.erl
@@ -27,6 +27,7 @@
%%----------------------------------------------------------------------
%% Include files
%%----------------------------------------------------------------------
+-include_lib("common_test/include/ct.hrl").
-include_lib("megaco/src/udp/megaco_udp.hrl").
-include("megaco_test_lib.hrl").
@@ -239,6 +240,8 @@ start_and_stop(doc) ->
["This test case sets up a connection and then cloises it. "
"No data is sent. "];
start_and_stop(Config) when is_list(Config) ->
+ Factor = ?config(megaco_factor, Config),
+ ct:timetrap(Factor * ?SECS(45)),
Pre = fun() ->
p("create nodes"),
ServerNode = make_node_name(server),
@@ -247,20 +250,22 @@ start_and_stop(Config) when is_list(Config) ->
ok = ?START_NODES(Nodes),
Nodes
end,
- Case = fun do_start_and_stop/1,
+ Case = fun(X) -> do_start_and_stop(Factor, X) end,
Post = fun(Nodes) ->
p("stop nodes"),
?STOP_NODES(lists:reverse(Nodes))
end,
try_tc(start_and_stop, Pre, Case, Post).
-do_start_and_stop([ServerNode, ClientNode]) ->
+do_start_and_stop(Factor, [ServerNode, ClientNode]) ->
%% Create command sequences
+ TOCalc = fun(BaseTO) -> to_calc(Factor, BaseTO) end,
+ TO = TOCalc(?SECS(5)),
p("create command sequences"),
ServerPort = 2944,
ServerCmds = start_and_stop_server_commands(ServerPort),
{ok, ServerHost} = inet:gethostname(),
- ClientCmds = start_and_stop_client_commands(ServerPort, ServerHost),
+ ClientCmds = start_and_stop_client_commands(TO, ServerPort, ServerHost),
%% Start the test procs used in the test-case, one for each node
p("start command handlers"),
@@ -268,8 +273,8 @@ do_start_and_stop([ServerNode, ClientNode]) ->
p("server command handler started: ~p", [Server]),
Client = client_start_command_handler(ClientNode, ClientCmds),
p("client command handler started: ~p", [Client]),
-
- ok =
+
+ ok =
receive
{operational, Server} ->
p("received listening message from server [~p] => "
@@ -280,11 +285,11 @@ do_start_and_stop([ServerNode, ClientNode]) ->
?SKIP(Reason);
{'EXIT', Client, {skip, Reason}} ->
?SKIP(Reason)
- after 5000 ->
+ after TO ->
{error, server_timeout}
end,
- ok = await_command_handler_completion([Server, Client], ?SECS(20)),
+ ok = await_command_handler_completion([Server, Client], TOCalc(?SECS(20))),
p("done"),
ok.
@@ -337,7 +342,7 @@ start_and_stop_server_commands(Port) ->
].
-start_and_stop_client_commands(ServerPort, _ServerHost) ->
+start_and_stop_client_commands(TO, ServerPort, _ServerHost) ->
Opts = [{port, ServerPort}],
Self = self(),
[
@@ -362,7 +367,7 @@ start_and_stop_client_commands(ServerPort, _ServerHost) ->
#{id => 4,
desc => "Await continue",
cmd => fun(State) ->
- client_await_continue_signal(State, 5000)
+ client_await_continue_signal(State, TO)
end},
#{id => 5,
@@ -399,6 +404,8 @@ sendreceive(suite) ->
sendreceive(doc) ->
["Test send and receive with the UDP transport. "];
sendreceive(Config) when is_list(Config) ->
+ Factor = ?config(megaco_factor, Config),
+ ct:timetrap(Factor * ?SECS(30)),
Pre = fun() ->
p("create nodes"),
ServerNode = make_node_name(server),
@@ -407,20 +414,22 @@ sendreceive(Config) when is_list(Config) ->
ok = ?START_NODES(Nodes),
Nodes
end,
- Case = fun do_sendreceive/1,
+ Case = fun(X) -> do_sendreceive(Factor, X) end,
Post = fun(Nodes) ->
p("stop nodes"),
?STOP_NODES(lists:reverse(Nodes))
end,
try_tc(sendreceive, Pre, Case, Post).
-do_sendreceive([ServerNode, ClientNode]) ->
+do_sendreceive(Factor, [ServerNode, ClientNode]) ->
%% Create command sequences
p("create command sequences"),
+ TOCalc = fun(BaseTO) -> to_calc(Factor, BaseTO) end,
+ TO = TOCalc(?SECS(5)),
ServerPort = 2944,
- ServerCmds = sendreceive_server_commands(ServerPort),
+ ServerCmds = sendreceive_server_commands(TO, ServerPort),
{ok, ServerHost} = inet:gethostname(),
- ClientCmds = sendreceive_client_commands(ServerPort, ServerHost),
+ ClientCmds = sendreceive_client_commands(TO, ServerPort, ServerHost),
%% Start the test procs used in the test-case, one for each node
p("start command handlers"),
@@ -440,16 +449,16 @@ do_sendreceive([ServerNode, ClientNode]) ->
?SKIP(Reason);
{'EXIT', Client, {skip, Reason}} ->
?SKIP(Reason)
- after 5000 ->
+ after TO ->
{error, server_timeout}
end,
- ok = await_command_handler_completion([Server, Client], ?SECS(20)),
+ ok = await_command_handler_completion([Server, Client], TOCalc(?SECS(20))),
p("done"),
ok.
-sendreceive_server_commands(Port) ->
+sendreceive_server_commands(TO, Port) ->
Opts = [{port, Port}],
Self = self(),
[
@@ -480,7 +489,7 @@ sendreceive_server_commands(Port) ->
#{id => 5,
desc => "Await initial message (ping)",
cmd => fun(State) ->
- server_await_initial_message(State, "ping", 5000)
+ server_await_initial_message(State, "ping", TO)
end},
#{id => 6,
@@ -492,7 +501,7 @@ sendreceive_server_commands(Port) ->
#{id => 7,
desc => "Await nothing before sending a message (hejsan)",
cmd => fun(State) ->
- server_await_nothing(State, 1000)
+ server_await_nothing(State, TO div 5)
end},
#{id => 8,
@@ -504,13 +513,13 @@ sendreceive_server_commands(Port) ->
#{id => 9,
desc => "Await reply (hoppsan) to message",
cmd => fun(State) ->
- server_await_message(State, "hoppsan", 1000)
+ server_await_message(State, "hoppsan", TO div 5)
end},
#{id => 10,
desc => "Await nothing before closing",
cmd => fun(State) ->
- server_await_nothing(State, 1000)
+ server_await_nothing(State, TO div 5)
end},
#{id => 11,
@@ -522,7 +531,7 @@ sendreceive_server_commands(Port) ->
#{id => 12,
desc => "Await nothing before stopping transport",
cmd => fun(State) ->
- server_await_nothing(State, 1000)
+ server_await_nothing(State, TO div 5)
end},
#{id => 13,
@@ -532,7 +541,7 @@ sendreceive_server_commands(Port) ->
end}
].
-sendreceive_client_commands(ServerPort, ServerHost) ->
+sendreceive_client_commands(TO, ServerPort, ServerHost) ->
OwnPort = ServerPort+1,
Opts = [{port, OwnPort}],
Self = self(),
@@ -558,7 +567,7 @@ sendreceive_client_commands(ServerPort, ServerHost) ->
#{id => 4,
desc => "Await continue",
cmd => fun(State) ->
- client_await_continue_signal(State, 5000)
+ client_await_continue_signal(State, TO)
end},
#{id => 5,
@@ -576,13 +585,13 @@ sendreceive_client_commands(ServerPort, ServerHost) ->
#{id => 7,
desc => "Await reply (pong) to initial message",
cmd => fun(State) ->
- client_await_message(State, "pong", 1000)
+ client_await_message(State, "pong", TO div 5)
end},
#{id => 8,
desc => "Await message (hejsan)",
cmd => fun(State) ->
- client_await_message(State, "hejsan", 5000)
+ client_await_message(State, "hejsan", TO)
end},
#{id => 9,
@@ -594,7 +603,7 @@ sendreceive_client_commands(ServerPort, ServerHost) ->
#{id => 10,
desc => "Await nothing before closing",
cmd => fun(State) ->
- client_await_nothing(State, 1000)
+ client_await_nothing(State, TO div 5)
end},
#{id => 11,
@@ -606,7 +615,7 @@ sendreceive_client_commands(ServerPort, ServerHost) ->
#{id => 12,
desc => "Await nothing before stopping transport",
cmd => fun(State) ->
- client_await_nothing(State, 1000)
+ client_await_nothing(State, TO div 5)
end},
#{id => 13,
@@ -624,6 +633,8 @@ block_unblock(suite) ->
block_unblock(doc) ->
["Test the block/unblock functions of the UDP transport. "];
block_unblock(Config) when is_list(Config) ->
+ Factor = ?config(megaco_factor, Config),
+ ct:timetrap(Factor * ?MINS(1)),
Pre = fun() ->
p("create nodes"),
ServerNode = make_node_name(server),
@@ -632,20 +643,22 @@ block_unblock(Config) when is_list(Config) ->
ok = ?START_NODES(Nodes),
Nodes
end,
- Case = fun do_block_unblock/1,
+ Case = fun(X) -> do_block_unblock(Factor, X) end,
Post = fun(Nodes) ->
p("stop nodes"),
?STOP_NODES(lists:reverse(Nodes))
end,
try_tc(block_unblock, Pre, Case, Post).
-do_block_unblock([ServerNode, ClientNode]) ->
+do_block_unblock(Factor, [ServerNode, ClientNode]) ->
%% Create command sequences
p("create command sequences"),
+ TOCalc = fun(BaseTO) -> to_calc(Factor, BaseTO) end,
+ TO = TOCalc(?SECS(5)),
ServerPort = 2944,
- ServerCmds = block_unblock_server_commands(ServerPort),
+ ServerCmds = block_unblock_server_commands(TO, ServerPort),
{ok, ServerHost} = inet:gethostname(),
- ClientCmds = block_unblock_client_commands(ServerPort, ServerHost),
+ ClientCmds = block_unblock_client_commands(TO, ServerPort, ServerHost),
%% Start the test procs used in the test-case, one for each node
p("start command handlers"),
@@ -667,7 +680,7 @@ do_block_unblock([ServerNode, ClientNode]) ->
?SKIP(Reason1);
{'EXIT', Client, {skip, Reason2}} ->
?SKIP(Reason2)
- after 5000 ->
+ after TO ->
{error, server_timeout}
end,
@@ -684,16 +697,16 @@ do_block_unblock([ServerNode, ClientNode]) ->
?SKIP(Reason3);
{'EXIT', Client, {skip, Reason4}} ->
?SKIP(Reason4)
- after 5000 ->
+ after TO ->
{error, timeout}
end,
- ok = await_command_handler_completion([Server, Client], ?SECS(20)),
+ ok = await_command_handler_completion([Server, Client], TOCalc(?SECS(20))),
p("done"),
ok.
-block_unblock_server_commands(Port) ->
+block_unblock_server_commands(TO, Port) ->
Opts = [{port, Port}],
Self = self(),
[
@@ -724,7 +737,7 @@ block_unblock_server_commands(Port) ->
#{id => 5,
desc => "Await initial message (ping)",
cmd => fun(State) ->
- server_await_initial_message(State, "ping", 5000)
+ server_await_initial_message(State, "ping", TO)
end},
#{id => 6,
@@ -736,7 +749,7 @@ block_unblock_server_commands(Port) ->
#{id => 7,
desc => "Await continue",
cmd => fun(State) ->
- server_await_continue_signal(State, 5000)
+ server_await_continue_signal(State, TO)
end},
#{id => 8,
@@ -748,19 +761,19 @@ block_unblock_server_commands(Port) ->
#{id => 9,
desc => "Await nothing before receiving (hoppsan) reply",
cmd => fun(State) ->
- server_await_nothing(State, 4000)
+ server_await_nothing(State, TO)
end},
#{id => 10,
desc => "Await reply (hoppsan) to message",
cmd => fun(State) ->
- server_await_message(State, "hoppsan", 2000)
+ server_await_message(State, "hoppsan", TO div 2)
end},
#{id => 11,
desc => "Await nothing before closing",
cmd => fun(State) ->
- server_await_nothing(State, 1000)
+ server_await_nothing(State, TO div 5)
end},
#{id => 12,
@@ -772,7 +785,7 @@ block_unblock_server_commands(Port) ->
#{id => 13,
desc => "Await nothing before stopping transport",
cmd => fun(State) ->
- server_await_nothing(State, 1000)
+ server_await_nothing(State, TO div 5)
end},
#{id => 14,
@@ -783,7 +796,7 @@ block_unblock_server_commands(Port) ->
].
-block_unblock_client_commands(ServerPort, ServerHost) ->
+block_unblock_client_commands(TO, ServerPort, ServerHost) ->
OwnPort = ServerPort+1,
Opts = [{port, OwnPort}],
Self = self(),
@@ -809,7 +822,7 @@ block_unblock_client_commands(ServerPort, ServerHost) ->
#{id => 4,
desc => "Await continue",
cmd => fun(State) ->
- client_await_continue_signal(State, 5000)
+ client_await_continue_signal(State, TO)
end},
#{id => 5,
@@ -827,7 +840,7 @@ block_unblock_client_commands(ServerPort, ServerHost) ->
#{id => 7,
desc => "Await reply (pong) to initial message",
cmd => fun(State) ->
- client_await_message(State, "pong", 1000)
+ client_await_message(State, "pong", TO div 5)
end},
#{id => 8,
@@ -845,7 +858,7 @@ block_unblock_client_commands(ServerPort, ServerHost) ->
#{id => 10,
desc => "Await nothing before unblocking",
cmd => fun(State) ->
- client_await_nothing(State, 5000)
+ client_await_nothing(State, TO)
end},
#{id => 11,
@@ -857,7 +870,7 @@ block_unblock_client_commands(ServerPort, ServerHost) ->
#{id => 8,
desc => "Await message (hejsan)",
cmd => fun(State) ->
- client_await_message(State, "hejsan", 5000)
+ client_await_message(State, "hejsan", TO)
end},
#{id => 9,
@@ -869,7 +882,7 @@ block_unblock_client_commands(ServerPort, ServerHost) ->
#{id => 10,
desc => "Await nothing before closing",
cmd => fun(State) ->
- client_await_nothing(State, 1000)
+ client_await_nothing(State, TO)
end},
#{id => 11,
@@ -881,7 +894,7 @@ block_unblock_client_commands(ServerPort, ServerHost) ->
#{id => 12,
desc => "Await nothing before stopping transport",
cmd => fun(State) ->
- client_await_nothing(State, 1000)
+ client_await_nothing(State, TO)
end},
#{id => 13,
@@ -974,14 +987,17 @@ server_start_transport(State) when is_map(State) ->
server_open(#{transport_ref := Ref} = State, Options)
when is_list(Options) ->
Opts = [{receive_handle, self()}, {module, ?MODULE} | Options],
- case (catch megaco_udp:open(Ref, Opts)) of
+ try megaco_udp:open(Ref, Opts) of
{ok, Socket, ControlPid} ->
{ok, State#{handle => {socket, Socket}, % Temporary
control_pid => ControlPid}};
{error, {could_not_open_udp_port, eaddrinuse}} ->
{skip, {server, eaddrinuse}};
- Error ->
- Error
+ {error, _} = ERROR ->
+ ERROR
+ catch
+ C:E:S ->
+ {error, {catched, C, E, S}}
end.
server_notify_operational(#{parent := Parent} = State) ->
@@ -1080,14 +1096,17 @@ client_start_transport(State) when is_map(State) ->
client_open(#{transport_ref := Ref} = State, Options)
when is_list(Options) ->
Opts = [{receive_handle, self()}, {module, ?MODULE} | Options],
- case (catch megaco_udp:open(Ref, Opts)) of
+ try megaco_udp:open(Ref, Opts) of
{ok, Socket, ControlPid} ->
{ok, State#{handle => {socket, Socket},
control_pid => ControlPid}};
{error, {could_not_open_udp_port, eaddrinuse}} ->
{skip, {client, eaddrinuse}};
- Error ->
- Error
+ {error, _} = ERROR ->
+ ERROR
+ catch
+ C:E:S ->
+ {error, {catched, C, E, S}}
end.
client_await_continue_signal(#{parent := Parent} = State, Timeout) ->
@@ -1196,6 +1215,13 @@ make_node_name(Name) ->
end.
+to_calc(1 = _Factor, BaseTO) when is_integer(BaseTO) andalso (BaseTO > 0) ->
+ BaseTO;
+to_calc(Factor, BaseTO) when is_integer(Factor) andalso (Factor > 0) andalso
+ is_integer(BaseTO) andalso (BaseTO > 0) ->
+ trunc( ((Factor + 1) / 2) * BaseTO ).
+
+
p(F) ->
p(F, []).
diff --git a/lib/megaco/vsn.mk b/lib/megaco/vsn.mk
index e9c21389bc..3a35c5d125 100644
--- a/lib/megaco/vsn.mk
+++ b/lib/megaco/vsn.mk
@@ -19,6 +19,6 @@
# %CopyrightEnd%
APPLICATION = megaco
-MEGACO_VSN = 3.18.8
+MEGACO_VSN = 3.19
PRE_VSN =
APP_VSN = "$(APPLICATION)-$(MEGACO_VSN)$(PRE_VSN)"
diff --git a/lib/mnesia/doc/specs/.gitignore b/lib/mnesia/doc/specs/.gitignore
new file mode 100644
index 0000000000..322eebcb06
--- /dev/null
+++ b/lib/mnesia/doc/specs/.gitignore
@@ -0,0 +1 @@
+specs_*.xml
diff --git a/lib/mnesia/doc/src/Makefile b/lib/mnesia/doc/src/Makefile
index f14fd33c7a..486993e76d 100644
--- a/lib/mnesia/doc/src/Makefile
+++ b/lib/mnesia/doc/src/Makefile
@@ -68,6 +68,7 @@ XML_GEN_FILES = $(XML_CHAPTER_GEN_FILES:%=$(XMLDIR)/%)
IMAGE_FILES = \
company.gif
-# ----------------------------------------------------
+SPECS_FILES = $(XML_REF3_FILES:%.xml=$(SPECDIR)/specs_%.xml)
+TOP_SPECS_FILE = specs.xml
include $(ERL_TOP)/make/doc.mk
diff --git a/lib/mnesia/doc/src/Mnesia_chap2.xmlsrc b/lib/mnesia/doc/src/Mnesia_chap2.xmlsrc
index a53bac020b..3c41945285 100644
--- a/lib/mnesia/doc/src/Mnesia_chap2.xmlsrc
+++ b/lib/mnesia/doc/src/Mnesia_chap2.xmlsrc
@@ -298,7 +298,7 @@
<p>The function <c>insert_emp/3</c> creates a Functional Object (Fun).
<c>Fun</c> is passed
as a single argument to the function
- <seemfa marker="mnesia#transaction/2">mnesia:transaction(Fun)</seemfa>.
+ <seemfa marker="mnesia#transaction/1">mnesia:transaction(Fun)</seemfa>.
This means that <c>Fun</c> is
run as a transaction with the following properties:</p>
<list type="bulleted">
diff --git a/lib/mnesia/doc/src/Mnesia_chap4.xmlsrc b/lib/mnesia/doc/src/Mnesia_chap4.xmlsrc
index c722646d33..335f09dd30 100644
--- a/lib/mnesia/doc/src/Mnesia_chap4.xmlsrc
+++ b/lib/mnesia/doc/src/Mnesia_chap4.xmlsrc
@@ -220,7 +220,7 @@
forced to release all its locks and sleep for a while. The Fun
in the transaction is evaluated once more.</p>
<p>It is therefore important that the code inside the Fun given to
- <seemfa marker="mnesia#transaction/2"><c>mnesia:transaction/1</c></seemfa>
+ <seemfa marker="mnesia#transaction/1"><c>mnesia:transaction/1</c></seemfa>
is pure. Some strange results can
occur if, for example, messages are sent by the transaction
Fun. The following example illustrates this situation:</p>
@@ -252,7 +252,7 @@
transaction. If no enclosing transaction (or other enclosing
<c>Mnesia</c> activity) exists, they all fail.</p>
<list type="bulleted">
- <item><seemfa marker="mnesia#transaction/2">mnesia:transaction(Fun) -> {aborted, Reason} |{atomic, Value}</seemfa>
+ <item><seemfa marker="mnesia#transaction/1">mnesia:transaction(Fun) -> {aborted, Reason} |{atomic, Value}</seemfa>
executes one transaction with the
functional object <c>Fun</c> as the single parameter.
</item>
@@ -498,14 +498,6 @@
<c>mnesia:dirty_prev/2</c> are synonyms.
</item>
<item>
- <p><seemfa marker="mnesia#dirty_slot/2">mnesia:dirty_slot(Tab, Slot)</seemfa>
- returns the list of records that are associated with <c>Slot</c>
- in a table. It can be used to traverse a table in a manner
- similar to the function <c>dirty_next/2</c>. A table has a
- number of slots that range from zero to some unknown upper
- bound. The function <c>dirty_slot/2</c> returns the special
- atom <c>'$end_of_table'</c> when the end of the table is
- reached.</p>
<p>The behavior of this function is undefined if the
table is written on while being
traversed. The function
@@ -664,7 +656,7 @@ mnesia:all_keys/1</seemfa>.
<p>As previously described, a Functional Object (Fun) performing
table access operations, as listed here, can be passed
on as arguments to the function
- <seemfa marker="mnesia#transaction/2">mnesia:transaction/1,2,3</seemfa>:
+ <seemfa marker="mnesia#transaction/1">mnesia:transaction/1,2,3</seemfa>:
</p>
<list type="bulleted">
<item>
@@ -732,7 +724,7 @@ mnesia:all_keys/1</seemfa>.
<item><c>ets</c></item>
</list>
<p>By passing the same "fun" as argument to the function
- <seemfa marker="mnesia#sync_transaction/3">mnesia:sync_transaction(Fun [, Args])</seemfa>
+ <seemfa marker="mnesia#sync_transaction/1">mnesia:sync_transaction(Fun [, Args])</seemfa>
it is performed
in synced transaction context. Synced transactions wait until all
active replicas has committed the transaction (to disc) before
diff --git a/lib/mnesia/doc/src/mnesia.xml b/lib/mnesia/doc/src/mnesia.xml
index 9097724012..1932b7695e 100644
--- a/lib/mnesia/doc/src/mnesia.xml
+++ b/lib/mnesia/doc/src/mnesia.xml
@@ -11,7 +11,7 @@
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
@@ -181,9 +181,67 @@
transaction before iterating over the table.</p>
</description>
+ <datatypes>
+ <datatype>
+ <name name="table"/>
+ </datatype>
+ <datatype>
+ <name name="activity"/>
+ </datatype>
+ <datatype>
+ <name name="create_option"/>
+ </datatype>
+ <datatype>
+ <name name="storage_type"/>
+ </datatype>
+ <datatype>
+ <name name="t_result" n_vars="1"/>
+ </datatype>
+ <datatype>
+ <name name="result"/>
+ </datatype>
+ <datatype>
+ <name name="index_attr"/>
+ </datatype>
+ <datatype>
+ <name name="write_locks"/>
+ </datatype>
+ <datatype>
+ <name name="read_locks"/>
+ </datatype>
+ <datatype>
+ <name name="lock_kind"/>
+ </datatype>
+ <datatype>
+ <name name="select_continuation"/>
+ </datatype>
+ <datatype>
+ <name name="snmp_struct"/>
+ </datatype>
+ <datatype>
+ <name name="snmp_type"/>
+ </datatype>
+ <datatype>
+ <name name="tuple_of" n_vars="1"/>
+ </datatype>
+ <datatype>
+ <name name="config_key"/>
+ </datatype>
+ <datatype>
+ <name name="config_value"/>
+ </datatype>
+ <datatype>
+ <name name="config_result"/>
+ </datatype>
+ <datatype>
+ <name name="debug_level"/>
+ </datatype>
+
+ </datatypes>
+
<funcs>
<func>
- <name since="">abort(Reason) -> transaction abort</name>
+ <name name="abort" arity="1" since=""/>
<fsummary>Terminates the current transaction.</fsummary>
<desc>
<p>Makes the transaction silently
@@ -195,7 +253,7 @@
</desc>
</func>
<func>
- <name since="">activate_checkpoint(Args) -> {ok,Name,Nodes} | {error,Reason}</name>
+ <name name="activate_checkpoint" arity="1" since=""/>
<fsummary>Activates a checkpoint.</fsummary>
<desc>
<marker id="activate_checkpoint"></marker>
@@ -259,7 +317,8 @@
</desc>
</func>
<func>
- <name since="">activity(AccessContext, Fun [, Args]) -> ResultOfFun | exit(Reason)</name>
+ <name name="activity" arity="2" since=""/>
+ <!--<name name="activity" arity="3" since=""/>-->
<fsummary>Executes <c>Fun</c> in <c>AccessContext</c>.</fsummary>
<desc>
<marker id="activity_2_3"></marker>
@@ -271,7 +330,7 @@
</desc>
</func>
<func>
- <name since="">activity(AccessContext, Fun, Args, AccessMod) -> ResultOfFun | exit(Reason)</name>
+ <name name="activity" arity="4" since=""/>
<fsummary>Executes <c>Fun</c> in <c>AccessContext</c>.</fsummary>
<desc>
<marker id="activity_4"></marker>
@@ -403,7 +462,7 @@
</desc>
</func>
<func>
- <name since="">add_table_copy(Tab, Node, Type) -> {aborted, R} | {atomic, ok}</name>
+ <name name="add_table_copy" arity="3" since=""/>
<fsummary>Copies a table to a remote node.</fsummary>
<desc>
<marker id="add_table_copy"></marker>
@@ -420,7 +479,7 @@ mnesia:add_table_copy(person, Node, disc_copies)</code>
</desc>
</func>
<func>
- <name since="">add_table_index(Tab, AttrName) -> {aborted, R} | {atomic, ok}</name>
+ <name name="add_table_index" arity="2" since=""/>
<fsummary>Creates an index for a table.</fsummary>
<desc>
<marker id="add_table_index"></marker>
@@ -441,7 +500,7 @@ mnesia:add_table_index(person, age)</code>
</desc>
</func>
<func>
- <name since="">all_keys(Tab) -> KeyList | transaction abort</name>
+ <name name="all_keys" arity="1" since=""/>
<fsummary>Returns all keys in a table.</fsummary>
<desc>
<marker id="all_keys"></marker>
@@ -453,7 +512,8 @@ mnesia:add_table_index(person, age)</code>
</desc>
</func>
<func>
- <name since="">async_dirty(Fun [, Args]) -> ResultOfFun | exit(Reason)</name>
+ <name name="async_dirty" arity="1" since=""/>
+ <name name="async_dirty" arity="2" since=""/>
<fsummary>Calls the <c>Fun</c> in a context that is not protected by a transaction.</fsummary>
<desc>
<marker id="async_dirty"></marker>
@@ -493,7 +553,8 @@ mnesia:add_table_index(person, age)</code>
</desc>
</func>
<func>
- <name since="">backup(Opaque [, BackupMod]) -> ok | {error,Reason}</name>
+ <name name="backup" arity="1" since=""/>
+ <name name="backup" arity="2" since=""/>
<fsummary>Backs up all tables in the database.</fsummary>
<desc>
<marker id="backup"></marker>
@@ -505,7 +566,8 @@ mnesia:add_table_index(person, age)</code>
</desc>
</func>
<func>
- <name since="">backup_checkpoint(Name, Opaque [, BackupMod]) -> ok | {error,Reason}</name>
+ <name name="backup_checkpoint" arity="2" since=""/>
+ <name name="backup_checkpoint" arity="3" since=""/>
<fsummary>Backs up all tables in a checkpoint.</fsummary>
<desc>
<marker id="backup_checkpoint"></marker>
@@ -520,7 +582,7 @@ mnesia:add_table_index(person, age)</code>
</desc>
</func>
<func>
- <name since="">change_config(Config, Value) -> {error, Reason} | {ok, ReturnValue}</name>
+ <name name="change_config" arity="2" since=""/>
<fsummary>Changes a configuration parameter.</fsummary>
<desc>
<marker id="change_config"></marker>
@@ -554,7 +616,7 @@ mnesia:add_table_index(person, age)</code>
</desc>
</func>
<func>
- <name since="">change_table_access_mode(Tab, AccessMode) -> {aborted, R} | {atomic, ok}</name>
+ <name name="change_table_access_mode" arity="2" since=""/>
<fsummary>Changes the access mode for the table.</fsummary>
<desc>
<marker id="change_table_access_mode"></marker>
@@ -568,7 +630,7 @@ mnesia:add_table_index(person, age)</code>
</desc>
</func>
<func>
- <name since="">change_table_copy_type(Tab, Node, To) -> {aborted, R} | {atomic, ok}</name>
+ <name name="change_table_copy_type" arity="3" since=""/>
<fsummary>Changes the storage type of a table.</fsummary>
<desc>
<marker id="change_table_copy_type"></marker>
@@ -585,7 +647,7 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code>
</desc>
</func>
<func>
- <name since="">change_table_load_order(Tab, LoadOrder) -> {aborted, R} | {atomic, ok}</name>
+ <name name="change_table_load_order" arity="2" since=""/>
<fsummary>Changes the load order priority for the table.</fsummary>
<desc>
<marker id="change_table_load_order"></marker>
@@ -595,7 +657,7 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code>
</desc>
</func>
<func>
- <name since="OTP R14B03">change_table_majority(Tab, Majority) -> {aborted, R} | {atomic, ok}</name>
+ <name name="change_table_majority" arity="2" since="OTP R14B03"/>
<fsummary>Changes the majority check setting for the table.</fsummary>
<desc>
<p><c>Majority</c> must be a boolean. Default is <c>false</c>.
@@ -607,7 +669,7 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code>
</desc>
</func>
<func>
- <name since="">clear_table(Tab) -> {aborted, R} | {atomic, ok}</name>
+ <name name="clear_table" arity="1" since=""/>
<fsummary>Deletes all entries in a table.</fsummary>
<desc>
<marker id="clear_table"></marker>
@@ -615,7 +677,7 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code>
</desc>
</func>
<func>
- <name since="">create_schema(DiscNodes) -> ok | {error,Reason}</name>
+ <name name="create_schema" arity="1" since=""/>
<fsummary>Creates a new schema on the specified nodes.</fsummary>
<desc>
<marker id="create_schema"></marker>
@@ -637,7 +699,7 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code>
</desc>
</func>
<func>
- <name since="">create_table(Name, TabDef) -> {atomic, ok} | {aborted, Reason}</name>
+ <name name="create_table" arity="2" since=""/>
<fsummary>Creates a Mnesia table called <c>Name</c>with properties as described by argument <c>TabDef</c>.</fsummary>
<desc>
<marker id="create_table"></marker>
@@ -799,7 +861,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">deactivate_checkpoint(Name) -> ok | {error, Reason}</name>
+ <name name="deactivate_checkpoint" arity="1" since=""/>
<fsummary>Deactivates a checkpoint.</fsummary>
<desc>
<marker id="deactivate_checkpoint"></marker>
@@ -811,7 +873,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">del_table_copy(Tab, Node) -> {aborted, R} | {atomic, ok}</name>
+ <name name="del_table_copy" arity="2" since=""/>
<fsummary>Deletes the replica of table <c>Tab</c> at node <c>Node</c>.</fsummary>
<desc>
<marker id="del_table_copy"></marker>
@@ -825,7 +887,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">del_table_index(Tab, AttrName) -> {aborted, R} | {atomic, ok}</name>
+ <name name="del_table_index" arity="2" since=""/>
<fsummary>Deletes an index in a table.</fsummary>
<desc>
<marker id="del_table_index"></marker>
@@ -834,16 +896,16 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">delete({Tab, Key}) -> transaction abort | ok</name>
+ <name name="delete" arity="1" since=""/>
<fsummary>Deletes all records in table <c>Tab</c> with the key <c>Key</c>.</fsummary>
<desc>
- <marker id="delete_2"></marker>
+ <marker id="delete_1"></marker>
<p>Calls <c>mnesia:delete(Tab, Key, write)</c>.</p>
</desc>
</func>
<func>
- <name since="">delete(Tab, Key, LockKind) -> transaction abort | ok</name>
- <fsummary>Deletes all records in table <c>Tab</c>with the key <c>Key</c>.</fsummary>
+ <name name="delete" arity="3" since=""/>
+ <fsummary>Deletes all records in table <c>Tab</c> with the key <c>Key</c>.</fsummary>
<desc>
<marker id="delete_3"></marker>
<p>Deletes all records in table <c>Tab</c> with the key
@@ -857,7 +919,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">delete_object(Record) -> transaction abort | ok</name>
+ <name name="delete_object" arity="1" since=""/>
<fsummary>Delete a record.</fsummary>
<desc>
<marker id="delete_object_1"></marker>
@@ -866,7 +928,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">delete_object(Tab, Record, LockKind) -> transaction abort | ok</name>
+ <name name="delete_object" arity="3" since=""/>
<fsummary>Deletes a record.</fsummary>
<desc>
<marker id="delete_object_3"></marker>
@@ -883,7 +945,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">delete_schema(DiscNodes) -> ok | {error,Reason}</name>
+ <name name="delete_schema" arity="1" since=""/>
<fsummary>Deletes the schema on the given nodes.</fsummary>
<desc>
<marker id="delete_schema"></marker>
@@ -904,7 +966,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">delete_table(Tab) -> {aborted, Reason} | {atomic, ok}</name>
+ <name name="delete_table" arity="1" since=""/>
<fsummary>Deletes permanently all replicas of table <c>Tab</c>.</fsummary>
<desc>
<marker id="delete_table"></marker>
@@ -912,7 +974,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_all_keys(Tab) -> KeyList | exit({aborted, Reason})</name>
+ <name name="dirty_all_keys" arity="1" since=""/>
<fsummary>Dirty search for all record keys in table.</fsummary>
<desc>
<marker id="delete_all_keys"></marker>
@@ -920,7 +982,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_delete({Tab, Key}) -> ok | exit({aborted, Reason})</name>
+ <name name="dirty_delete" arity="1" since=""/>
<fsummary>Dirty delete of a record.</fsummary>
<desc>
<marker id="dirty_delete"></marker>
@@ -928,14 +990,14 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_delete(Tab, Key) -> ok | exit({aborted, Reason})</name>
+ <name name="dirty_delete" arity="2" since=""/>
<fsummary>Dirty delete of a record.</fsummary>
<desc>
<p>Dirty equivalent of the function <c>mnesia:delete/3</c>.</p>
</desc>
</func>
<func>
- <name since="">dirty_delete_object(Record)</name>
+ <name name="dirty_delete_object" arity="1" since=""/>
<fsummary>Dirty delete of a record.</fsummary>
<desc>
<marker id="dirty_delete_object_1"></marker>
@@ -944,18 +1006,18 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_delete_object(Tab, Record)</name>
+ <name name="dirty_delete_object" arity="2" since=""/>
<fsummary>Dirty delete of a record.</fsummary>
<desc>
<p>Dirty equivalent of the function <c>mnesia:delete_object/3</c>.</p>
</desc>
</func>
<func>
- <name since="">dirty_first(Tab) -> Key | exit({aborted, Reason})</name>
+ <name name="dirty_first" arity="1" since=""/>
<fsummary>Returns the key for the first record in a table.</fsummary>
<desc>
<marker id="dirty_first"></marker>
- <p>Records in <c>set</c> or <c>bag</c> tables are not ordered.
+ <p>Records in <c>set</c> or <c>bag</c> tables are not ordered.
However, there is an ordering of the records that is unknown
to the user. Therefore, a table can be traversed by this
function with the function <c>mnesia:dirty_next/2</c>.
@@ -967,7 +1029,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_index_match_object(Pattern, Pos)</name>
+ <name name="dirty_index_match_object" arity="2" since=""/>
<fsummary>Dirty pattern match using index.</fsummary>
<desc>
<marker id="dirty_index_match_object_2"></marker>
@@ -977,7 +1039,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_index_match_object(Tab, Pattern, Pos)</name>
+ <name name="dirty_index_match_object" arity="3" since=""/>
<fsummary>Dirty pattern match using index.</fsummary>
<desc>
<p>Dirty equivalent of the function
@@ -985,7 +1047,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_index_read(Tab, SecondaryKey, Pos)</name>
+ <name name="dirty_index_read" arity="3" since=""/>
<fsummary>Dirty read using index.</fsummary>
<desc>
<marker id="dirty_index_read"></marker>
@@ -994,7 +1056,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_last(Tab) -> Key | exit({aborted, Reason})</name>
+ <name name="dirty_last" arity="1" since=""/>
<fsummary>Returns the key for the last record in a table.</fsummary>
<desc>
<marker id="dirty_last"></marker>
@@ -1006,7 +1068,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_match_object(Pattern) -> RecordList | exit({aborted, Reason})</name>
+ <name name="dirty_match_object" arity="1" since=""/>
<fsummary>Dirty pattern match pattern.</fsummary>
<desc>
<marker id="dirty_match_object_1"></marker>
@@ -1015,7 +1077,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_match_object(Tab, Pattern) -> RecordList | exit({aborted, Reason})</name>
+ <name name="dirty_match_object" arity="2" since=""/>
<fsummary>Dirty pattern match pattern.</fsummary>
<desc>
<p>Dirty equivalent of the function
@@ -1023,7 +1085,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_next(Tab, Key) -> Key | exit({aborted, Reason})</name>
+ <name name="dirty_next" arity="2" since=""/>
<fsummary>Return the next key in a table.</fsummary>
<desc>
<marker id="dirty_next"></marker>
@@ -1038,7 +1100,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_prev(Tab, Key) -> Key | exit({aborted, Reason})</name>
+ <name name="dirty_prev" arity="2" since=""/>
<fsummary>Returns the previous key in a table.</fsummary>
<desc>
<marker id="dirty_prev"></marker>
@@ -1050,7 +1112,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_read({Tab, Key}) -> ValueList | exit({aborted, Reason}</name>
+ <name name="dirty_read" arity="1" since=""/>
<fsummary>Dirty read of records.</fsummary>
<desc>
<marker id="dirty_read"></marker>
@@ -1058,14 +1120,14 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_read(Tab, Key) -> ValueList | exit({aborted, Reason}</name>
+ <name name="dirty_read" arity="2" since=""/>
<fsummary>Dirty read of records.</fsummary>
<desc>
<p>Dirty equivalent of the function <c>mnesia:read/3</c>.</p>
</desc>
</func>
<func>
- <name since="">dirty_select(Tab, MatchSpec) -> ValueList | exit({aborted, Reason}</name>
+ <name name="dirty_select" arity="2" since=""/>
<fsummary>Dirty matches the objects in <c>Tab</c> against <c>MatchSpec</c>.</fsummary>
<desc>
<marker id="dirty_select"></marker>
@@ -1073,23 +1135,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_slot(Tab, Slot) -> RecordList | exit({aborted, Reason})</name>
- <fsummary>Returns the list of records that are associated with <c>Slot</c> in a table.</fsummary>
- <desc>
- <marker id="dirty_slot"></marker>
- <p>Traverses a table in a
- manner similar to the function <c>mnesia:dirty_next/2</c>.
- A table has a number of slots that range from 0 (zero) to
- an unknown upper bound. The function
- <c>mnesia:dirty_slot/2</c> returns the special atom
- <c>'$end_of_table'</c> when the end of the table is reached.
- The behavior of this function is undefined if a write
- operation is performed on the table while it is being
- traversed.</p>
- </desc>
- </func>
- <func>
- <name since="">dirty_update_counter({Tab, Key}, Incr) -> NewVal | exit({aborted, Reason})</name>
+ <name name="dirty_update_counter" arity="2" since=""/>
<fsummary>Dirty update of a counter record.</fsummary>
<desc>
<marker id="dirty_update_counter"></marker>
@@ -1097,7 +1143,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_update_counter(Tab, Key, Incr) -> NewVal | exit({aborted, Reason})</name>
+ <name name="dirty_update_counter" arity="3" since=""/>
<fsummary>Dirty update of a counter record.</fsummary>
<desc>
<p>Mnesia has no special counter records. However,
@@ -1126,7 +1172,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_write(Record) -> ok | exit({aborted, Reason})</name>
+ <name name="dirty_write" arity="1" since=""/>
<fsummary>Dirty write of a record.</fsummary>
<desc>
<marker id="dirty_write_1"></marker>
@@ -1135,14 +1181,14 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_write(Tab, Record) -> ok | exit({aborted, Reason})</name>
+ <name name="dirty_write" arity="2" since=""/>
<fsummary>Dirty write of a record.</fsummary>
<desc>
<p>Dirty equivalent of the function <c>mnesia:write/3</c>.</p>
</desc>
</func>
<func>
- <name since="">dump_log() -> dumped</name>
+ <name name="dump_log" arity="0" since=""/>
<fsummary>Performs a user-initiated dump of the local log file.</fsummary>
<desc>
<marker id="dump_log"></marker>
@@ -1156,7 +1202,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dump_tables(TabList) -> {atomic, ok} | {aborted, Reason}</name>
+ <name name="dump_tables" arity="1" since=""/>
<fsummary>Dumps all RAM tables to disc.</fsummary>
<desc>
<marker id="dump_tables"></marker>
@@ -1168,7 +1214,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dump_to_textfile(Filename)</name>
+ <name name="dump_to_textfile" arity="1" since=""/>
<fsummary>Dumps local tables into a text file.</fsummary>
<desc>
<marker id="dump_to_textfile"></marker>
@@ -1181,7 +1227,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">error_description(Error) -> String</name>
+ <name name="error_description" arity="1" since=""/>
<fsummary>Returns a string describing a particular Mnesia error.</fsummary>
<desc>
<marker id="error_description"></marker>
@@ -1259,7 +1305,8 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">ets(Fun [, Args]) -> ResultOfFun | exit(Reason)</name>
+ <name name="ets" arity="1" since=""/>
+ <name name="ets" arity="2" since=""/>
<fsummary>Calls the <c>Fun</c> in a raw context that is not protected by a transaction.</fsummary>
<desc>
<marker id="ets"></marker>
@@ -1278,7 +1325,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">first(Tab) -> Key | transaction abort</name>
+ <name name="first" arity="1" since=""/>
<fsummary>Returns the key for the first record in a table.</fsummary>
<desc>
<marker id="first"></marker>
@@ -1293,7 +1340,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">foldl(Function, Acc, Table) -> NewAcc | transaction abort</name>
+ <name name="foldl" arity="3" since=""/>
<fsummary>Calls <c>Function</c> for each record in <c>Table</c>.</fsummary>
<desc>
<marker id="foldl"></marker>
@@ -1306,7 +1353,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">foldr(Function, Acc, Table) -> NewAcc | transaction abort</name>
+ <name name="foldr" arity="3" since=""/>
<fsummary>Calls <c>Function</c> for each record in <c>Table</c>.</fsummary>
<desc>
<marker id="foldr"></marker>
@@ -1317,7 +1364,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">force_load_table(Tab) -> yes | ErrorDescription</name>
+ <name name="force_load_table" arity="1" since=""/>
<fsummary>Forces a table to be loaded into the system.</fsummary>
<desc>
<marker id="force_load_table"></marker>
@@ -1335,7 +1382,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">index_match_object(Pattern, Pos) -> transaction abort | ObjList</name>
+ <name name="index_match_object" arity="2" since=""/>
<fsummary>Matches records and uses index information.</fsummary>
<desc>
<marker id="index_match_object_2"></marker>
@@ -1345,7 +1392,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">index_match_object(Tab, Pattern, Pos, LockKind) -> transaction abort | ObjList</name>
+ <name name="index_match_object" arity="4" since=""/>
<fsummary>Matches records and uses index information.</fsummary>
<desc>
<marker id="index_match_object_4"></marker>
@@ -1377,7 +1424,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">index_read(Tab, SecondaryKey, Pos) -> transaction abort | RecordList</name>
+ <name name="index_read" arity="3" since=""/>
<fsummary>Reads records through index table.</fsummary>
<desc>
<marker id="index_read"></marker>
@@ -1397,7 +1444,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">info() -> ok</name>
+ <name name="info" arity="0" since=""/>
<fsummary>Prints system information on the terminal.</fsummary>
<desc>
<marker id="info"></marker>
@@ -1408,7 +1455,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">install_fallback(Opaque) -> ok | {error,Reason}</name>
+ <name name="install_fallback" arity="1" since=""/>
<fsummary>Installs a backup as fallback.</fsummary>
<desc>
<marker id="install_fallback_1"></marker>
@@ -1417,7 +1464,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">install_fallback(Opaque), BackupMod) -> ok | {error,Reason}</name>
+ <name name="install_fallback" arity="1" since=""/>
<fsummary>Installs a backup as fallback.</fsummary>
<desc>
<p>Calls <c>mnesia:install_fallback(Opaque, Args)</c>, where
@@ -1425,7 +1472,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">install_fallback(Opaque, Args) -> ok | {error,Reason}</name>
+ <name name="install_fallback" arity="2" since=""/>
<fsummary>Installs a backup as fallback.</fsummary>
<desc>
<p>Installs a backup as fallback. The fallback is used to
@@ -1483,7 +1530,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">is_transaction() -> boolean</name>
+ <name name="is_transaction" arity="0" since=""/>
<fsummary>Checks if code is running in a transaction.</fsummary>
<desc>
<marker id="is_transaction"></marker>
@@ -1492,7 +1539,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">last(Tab) -> Key | transaction abort</name>
+ <name name="last" arity="1" since=""/>
<fsummary>Returns the key for the last record in a table.</fsummary>
<desc>
<p>Works exactly like
@@ -1503,7 +1550,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">load_textfile(Filename)</name>
+ <name name="load_textfile" arity="1" since=""/>
<fsummary>Loads tables from a text file.</fsummary>
<desc>
<marker id="load_textfile"></marker>
@@ -1516,7 +1563,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">lock(LockItem, LockKind) -> Nodes | ok | transaction abort</name>
+ <name name="lock" arity="2" since=""/>
<fsummary>Explicit grab lock.</fsummary>
<desc>
<marker id="lock"></marker>
@@ -1605,7 +1652,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">match_object(Pattern) -> transaction abort | RecList</name>
+ <name name="match_object" arity="1" since=""/>
<fsummary>Matches <c>Pattern</c> for records.</fsummary>
<desc>
<marker id="match_object_1"></marker>
@@ -1614,7 +1661,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">match_object(Tab, Pattern, LockKind) -> transaction abort | RecList</name>
+ <name name="match_object" arity="3" since=""/>
<fsummary>Matches <c>Pattern</c> for records.</fsummary>
<desc>
<marker id="match_object_3"></marker>
@@ -1639,7 +1686,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">move_table_copy(Tab, From, To) -> {aborted, Reason} | {atomic, ok}</name>
+ <name name="move_table_copy" arity="3" since=""/>
<fsummary>Moves the copy of table <c>Tab</c> from node <c>From</c> to node <c>To</c>.</fsummary>
<desc>
<marker id="move_table_copy"></marker>
@@ -1653,7 +1700,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">next(Tab, Key) -> Key | transaction abort</name>
+ <name name="next" arity="2" since=""/>
<fsummary>Returns the next key in a table.</fsummary>
<desc>
<marker id="next"></marker>
@@ -1665,7 +1712,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">prev(Tab, Key) -> Key | transaction abort</name>
+ <name name="prev" arity="2" since=""/>
<fsummary>Returns the previous key in a table.</fsummary>
<desc>
<p>Works exactly like
@@ -1676,7 +1723,8 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">read({Tab, Key}) -> transaction abort | RecordList</name>
+ <name name="read" arity="1" since=""/>
+ <name name="read" arity="2" since=""/>
<fsummary>Reads records(s) with a given key.</fsummary>
<desc>
<marker id="read_2"></marker>
@@ -1684,14 +1732,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">read(Tab, Key) -> transaction abort | RecordList</name>
- <fsummary>Reads records(s) with a given key.</fsummary>
- <desc>
- <p>Calls function <c>mnesia:read(Tab, Key, read)</c>.</p>
- </desc>
- </func>
- <func>
- <name since="">read(Tab, Key, LockKind) -> transaction abort | RecordList</name>
+ <name name="read" arity="3" since=""/>
<fsummary>Reads records(s) with a given key.</fsummary>
<desc>
<marker id="read_3"></marker>
@@ -1716,7 +1757,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">read_lock_table(Tab) -> ok | transaction abort</name>
+ <name name="read_lock_table" arity="1" since=""/>
<fsummary>Sets a read lock on an entire table.</fsummary>
<desc>
<marker id="read_lock_table"></marker>
@@ -1725,7 +1766,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">report_event(Event) -> ok</name>
+ <name name="report_event" arity="1" since=""/>
<fsummary>Reports a user event to the Mnesia event handler.</fsummary>
<desc>
<marker id="report_event"></marker>
@@ -1743,7 +1784,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">restore(Opaque, Args) -> {atomic, RestoredTabs} |{aborted, Reason}</name>
+ <name name="restore" arity="2" since=""/>
<fsummary>Online restore of backup.</fsummary>
<desc>
<marker id="restore"></marker>
@@ -1803,7 +1844,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">s_delete({Tab, Key}) -> ok | transaction abort</name>
+ <name name="s_delete" arity="1" since=""/>
<fsummary>Sets sticky lock and delete records.</fsummary>
<desc>
<marker id="s_delete"></marker>
@@ -1812,7 +1853,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">s_delete_object(Record) -> ok | transaction abort</name>
+ <name name="s_delete_object" arity="1" since=""/>
<fsummary>Sets sticky lock and delete record.</fsummary>
<desc>
<marker id="s_delete_object"></marker>
@@ -1822,7 +1863,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">s_write(Record) -> ok | transaction abort</name>
+ <name name="s_write" arity="1" since=""/>
<fsummary>Writes <c>Record</c> and sets sticky lock.</fsummary>
<desc>
<marker id="s_write"></marker>
@@ -1832,21 +1873,22 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">schema() -> ok</name>
+ <name name="schema" arity="0" since=""/>
<fsummary>Prints information about all table definitions on the terminal.</fsummary>
<desc>
<p>Prints information about all table definitions on the terminal.</p>
</desc>
</func>
<func>
- <name since="">schema(Tab) -> ok</name>
+ <name name="schema" arity="1" since=""/>
<fsummary>Prints information about one table definition on the terminal.</fsummary>
<desc>
<p>Prints information about one table definition on the terminal.</p>
</desc>
</func>
<func>
- <name since="">select(Tab, MatchSpec [, Lock]) -> transaction abort | [Object]</name>
+ <name name="select" arity="2" since=""/>
+ <name name="select" arity="3" since=""/>
<fsummary>Matches the objects in <c>Tab</c> against <c>MatchSpec</c>.</fsummary>
<desc>
<marker id="select_2_3"></marker>
@@ -1884,7 +1926,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name since="">select(Tab, MatchSpec, NObjects, Lock) -> transaction abort | {[Object],Cont} | '$end_of_table'</name>
+ <name name="select" arity="4" since=""/>
<fsummary>Matches the objects in <c>Tab</c> against <c>MatchSpec</c>.</fsummary>
<desc>
<marker id="select_4"></marker>
@@ -1907,7 +1949,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name since="">select(Cont) -> transaction abort | {[Object],Cont} | '$end_of_table'</name>
+ <name name="select" arity="1" since=""/>
<fsummary>Continues selecting objects.</fsummary>
<desc>
<p>Selects more objects with the match specification initiated
@@ -1919,7 +1961,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name since="">set_debug_level(Level) -> OldLevel</name>
+ <name name="set_debug_level" arity="1" since=""/>
<fsummary>Changes the internal debug level of Mnesia.</fsummary>
<desc>
<marker id="set_debug_level"></marker>
@@ -1930,7 +1972,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name since="">set_master_nodes(MasterNodes) -> ok | {error, Reason}</name>
+ <name name="set_master_nodes" arity="1" since=""/>
<fsummary>Sets the master nodes for all tables.</fsummary>
<desc>
<marker id="set_master_nodes_1"></marker>
@@ -1943,7 +1985,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name since="">set_master_nodes(Tab, MasterNodes) -> ok | {error, Reason}</name>
+ <name name="set_master_nodes" arity="2" since=""/>
<fsummary>Sets the master nodes for a table.</fsummary>
<desc>
<marker id="set_master_nodes_2"></marker>
@@ -1968,14 +2010,14 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name since="">snmp_close_table(Tab) -> {aborted, R} | {atomic, ok}</name>
+ <name name="snmp_close_table" arity="1" since=""/>
<fsummary>Removes the possibility for SNMP to manipulate the table.</fsummary>
<desc>
<p>Removes the possibility for SNMP to manipulate the table.</p>
</desc>
</func>
<func>
- <name since="">snmp_get_mnesia_key(Tab, RowIndex) -> {ok, Key} | undefined</name>
+ <name name="snmp_get_mnesia_key" arity="2" since=""/>
<fsummary>Gets the corresponding Mnesia key from an SNMP index.</fsummary>
<type>
<v>Tab ::= atom()</v>
@@ -1990,7 +2032,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name since="">snmp_get_next_index(Tab, RowIndex) -> {ok, NextIndex} | endOfTable</name>
+ <name name="snmp_get_next_index" arity="2" since=""/>
<fsummary>Gets the index of the next lexicographical row.</fsummary>
<type>
<v>Tab ::= atom()</v>
@@ -2006,7 +2048,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name since="">snmp_get_row(Tab, RowIndex) -> {ok, Row} | undefined</name>
+ <name name="snmp_get_row" arity="2" since=""/>
<fsummary>Retrieves a row indexed by an SNMP index.</fsummary>
<type>
<v>Tab ::= atom()</v>
@@ -2019,7 +2061,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name since="">snmp_open_table(Tab, SnmpStruct) -> {aborted, R} | {atomic, ok}</name>
+ <name name="snmp_open_table" arity="2" since=""/>
<fsummary>Organizes a Mnesia table as an SNMP table.</fsummary>
<type>
<v>Tab ::= atom()</v>
@@ -2073,7 +2115,7 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name since="">start() -> ok | {error, Reason}</name>
+ <name name="start" arity="0" since=""/>
<fsummary>Starts a local Mnesia system.</fsummary>
<desc>
<marker id="start"></marker>
@@ -2115,7 +2157,7 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name since="">stop() -> stopped</name>
+ <name name="stop" arity="0" since=""/>
<fsummary>Stops Mnesia locally.</fsummary>
<desc>
<marker id="stop"></marker>
@@ -2124,7 +2166,7 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name since="">subscribe(EventCategory) -> {ok, Node} | {error, Reason}</name>
+ <name name="subscribe" arity="1" since=""/>
<fsummary>Subscribes to events of type <c>EventCategory</c>.</fsummary>
<desc>
<marker id="subscribe"></marker>
@@ -2134,7 +2176,8 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name since="">sync_dirty(Fun [, Args]) -> ResultOfFun | exit(Reason)</name>
+ <name name="sync_dirty" arity="1" since=""/>
+ <name name="sync_dirty" arity="2" since=""/>
<fsummary>Calls the <c>Fun</c> in a context that is not protected by a transaction.</fsummary>
<desc>
<marker id="sync_dirty"></marker>
@@ -2150,7 +2193,7 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name since="OTP 17.0">sync_log() -> ok | {error, Reason}</name>
+ <name name="sync_log" arity="0" since="OTP 17.0"/>
<fsummary>Performs a file sync of the local log file.</fsummary>
<desc>
<p>Ensures that the local transaction log file is synced to disk.
@@ -2160,7 +2203,10 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name since="">sync_transaction(Fun [, Args [, Retries]]) -> {aborted, Reason} | {atomic, ResultOfFun}</name>
+ <name name="sync_transaction" arity="1" since=""/>
+ <name name="sync_transaction" arity="2" clause_i="1" since=""/>
+ <name name="sync_transaction" arity="2" clause_i="2" since=""/>
+ <name name="sync_transaction" arity="3" since=""/>
<fsummary>Synchronously executes a transaction.</fsummary>
<desc>
<marker id="sync_transaction"></marker>
@@ -2173,7 +2219,7 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name since="">system_info(InfoKey) -> Info | exit({aborted, Reason})</name>
+ <name name="system_info" arity="1" since=""/>
<fsummary>Returns information about the Mnesia system.</fsummary>
<desc>
<marker id="system_info"></marker>
@@ -2360,7 +2406,8 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name since="">table(Tab [,[Option]]) -> QueryHandle</name>
+ <name name="table" arity="1" since=""/>
+ <name name="table" arity="2" since=""/>
<fsummary>Return a QLC query handle.</fsummary>
<desc>
<marker id="table"></marker>
@@ -2415,7 +2462,7 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name since="">table_info(Tab, InfoKey) -> Info | exit({aborted, Reason})</name>
+ <name name="table_info" arity="2" since=""/>
<fsummary>Returns local information about table.</fsummary>
<desc>
<marker id="table_info"></marker>
@@ -2571,7 +2618,11 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name since="">transaction(Fun [, Args [, Retries]]) -> {aborted, Reason} | {atomic, ResultOfFun}</name>
+ <name name="transaction" arity="1" since=""/>
+ <name name="transaction" arity="2" clause_i="1" since=""/>
+ <name name="transaction" arity="2" clause_i="2" since=""/>
+ <name name="transaction" arity="3" since=""/>
+
<fsummary>Executes a transaction.</fsummary>
<desc>
<marker id="transaction"></marker>
@@ -2593,8 +2644,8 @@ mnesia:create_table(employee,
<code type="none">
add_family({family, F, M, Children}) ->
ChildOids = lists:map(fun oid/1, Children),
- Trans = fun() ->
- mnesia:write(F#person{children = ChildOids},
+ Trans = fun() ->
+ mnesia:write(F#person{children = ChildOids},
mnesia:write(M#person{children = ChildOids},
Write = fun(Child) -> mnesia:write(Child) end,
lists:foreach(Write, Children)
@@ -2639,7 +2690,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name since="">transform_table(Tab, Fun, NewAttributeList, NewRecordName) -> {aborted, R} | {atomic, ok}</name>
+ <name name="transform_table" arity="4" since=""/>
<fsummary>Changes format on all records in table <c>Tab</c>.</fsummary>
<desc>
<marker id="transform_table_4"></marker>
@@ -2660,7 +2711,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name since="">transform_table(Tab, Fun, NewAttributeList) -> {aborted, R} | {atomic, ok}</name>
+ <name name="transform_table" arity="3" since=""/>
<fsummary>Changes format on all records in table <c>Tab</c>.</fsummary>
<desc>
<p>Calls <c>mnesia:transform_table(Tab, Fun,
@@ -2669,7 +2720,8 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name since="">traverse_backup(Source, [SourceMod,] Target, [TargetMod,] Fun, Acc) -> {ok, LastAcc} | {error, Reason}</name>
+ <name name="traverse_backup" arity="4" since=""/>
+ <name name="traverse_backup" arity="6" since=""/>
<fsummary>Traversal of a backup.</fsummary>
<desc>
<marker id="traverse_backup"></marker>
@@ -2691,7 +2743,7 @@ raise(Name, Amount) ->
<c>{BackupItems,NewAcc}</c>, where <c>BackupItems</c> is
a list of valid backup items, and <c>NewAcc</c> is a new
accumulator value. The returned backup items are written
- in the target backup.
+ in the target backup.
</item>
<item><c>LastAcc</c> is the last accumulator value. This is
the last <c>NewAcc</c> value that was returned by <c>Fun</c>.
@@ -2700,7 +2752,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name since="">uninstall_fallback() -> ok | {error,Reason}</name>
+ <name name="uninstall_fallback" arity="0" since=""/>
<fsummary>Uninstalls a fallback.</fsummary>
<desc>
<marker id="uninstall_fallback_0"></marker>
@@ -2709,7 +2761,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name since="">uninstall_fallback(Args) -> ok | {error,Reason}</name>
+ <name name="uninstall_fallback" arity="1" since=""/>
<fsummary>Uninstalls a fallback.</fsummary>
<desc>
<p>Deinstalls a fallback before it
@@ -2736,7 +2788,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name since="">unsubscribe(EventCategory) -> {ok, Node} | {error, Reason}</name>
+ <name name="unsubscribe" arity="1" since=""/>
<fsummary>Subscribes to events of type <c>EventCategory</c>.</fsummary>
<desc>
<marker id="unsubscribe"></marker>
@@ -2746,7 +2798,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name since="">wait_for_tables(TabList, Timeout) -> ok | {timeout, BadTabList} | {error, Reason}</name>
+ <name name="wait_for_tables" arity="2" since=""/>
<fsummary>Waits for tables to be accessible.</fsummary>
<desc>
<marker id="wait_for_tables"></marker>
@@ -2757,7 +2809,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name since="">wread({Tab, Key}) -> transaction abort | RecordList</name>
+ <name name="wread" arity="1" since=""/>
<fsummary>Reads records with given key.</fsummary>
<desc>
<marker id="wread"></marker>
@@ -2765,7 +2817,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name since="">write(Record) -> transaction abort | ok</name>
+ <name name="write" arity="1" since=""/>
<fsummary>Writes a record into the database.</fsummary>
<desc>
<marker id="write_1"></marker>
@@ -2774,7 +2826,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name since="">write(Tab, Record, LockKind) -> transaction abort | ok</name>
+ <name name="write" arity="3" since=""/>
<fsummary>Writes a record into the database.</fsummary>
<desc>
<marker id="write_3"></marker>
@@ -2790,7 +2842,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name since="">write_lock_table(Tab) -> ok | transaction abort</name>
+ <name name="write_lock_table" arity="1" since=""/>
<fsummary>Sets write lock on an entire table.</fsummary>
<desc>
<marker id="write_lock_table"></marker>
@@ -2874,7 +2926,7 @@ raise(Name, Amount) ->
<item>
<p><c>-mnesia dc_dump_limit Number</c>. Controls how often
<c>disc_copies</c> tables are dumped from memory.
- Tables are dumped when
+ Tables are dumped when
<c>filesize(Log) > (filesize(Tab)/Dc_dump_limit)</c>.
Lower values reduce CPU overhead but increase disk space
and startup times. Default is 4.</p>
@@ -3037,6 +3089,6 @@ raise(Name, Amount) ->
<seeerl marker="mnesia:mnesia_registry">mnesia_registry(3)</seeerl>,
<seeerl marker="stdlib:qlc">qlc(3)</seeerl></p>
</section>
-
+
</erlref>
diff --git a/lib/mnesia/doc/src/notes.xml b/lib/mnesia/doc/src/notes.xml
index 5cda5ed72e..2fd59e6496 100644
--- a/lib/mnesia/doc/src/notes.xml
+++ b/lib/mnesia/doc/src/notes.xml
@@ -39,7 +39,51 @@
thus constitutes one section in this document. The title of each
section is the version number of Mnesia.</p>
- <section><title>Mnesia 4.16.3</title>
+ <section><title>Mnesia 4.17</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Make <c>mnesia:create_table/2</c> return correct badarg
+ value.</p>
+ <p>
+ Own Id: OTP-16072 Aux Id: PR-2320 </p>
+ </item>
+ <item>
+ <p>
+ Fixed a bug where mnesia was sometimes not waiting during
+ start for a commit decision on asymmetric transactions.</p>
+ <p>
+ Own Id: OTP-16634 Aux Id: PR-2610 ERL-1227 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Remove usage and documentation of old requests of the
+ I/O-protocol.</p>
+ <p>
+ Own Id: OTP-15695</p>
+ </item>
+ <item>
+ <p>
+ Avoid using <c>rpc</c> calls to do table reads, which
+ will reduce the load on rpc server and improve
+ performance.</p>
+ <p>
+ Own Id: OTP-16189</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Mnesia 4.16.3</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/mnesia/doc/src/specs.xml b/lib/mnesia/doc/src/specs.xml
new file mode 100644
index 0000000000..3d33a1e611
--- /dev/null
+++ b/lib/mnesia/doc/src/specs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<specs xmlns:xi="http://www.w3.org/2001/XInclude">
+ <xi:include href="../specs/specs_mnesia.xml"/>
+ <xi:include href="../specs/specs_mnesia_frag_hash.xml"/>
+ <xi:include href="../specs/specs_mnesia_registry.xml"/>
+</specs>
diff --git a/lib/mnesia/src/mnesia.erl b/lib/mnesia/src/mnesia.erl
index 560ebca824..3f1b173e37 100644
--- a/lib/mnesia/src/mnesia.erl
+++ b/lib/mnesia/src/mnesia.erl
@@ -155,6 +155,7 @@
{'user_properties', proplists:proplist()}.
-type t_result(Res) :: {'atomic', Res} | {'aborted', Reason::term()}.
+-type result() :: ok | {'error', Reason::term()}.
-type activity() :: 'ets' | 'async_dirty' | 'sync_dirty' | 'transaction' | 'sync_transaction' |
{'transaction', Retries::non_neg_integer()} |
{'sync_transaction', Retries::non_neg_integer()}.
@@ -171,6 +172,7 @@
-type config_key() :: extra_db_nodes | dc_dump_limit.
-type config_value() :: [node()] | number().
-type config_result() :: {ok, config_value()} | {error, term()}.
+-type debug_level() :: 'none' | 'verbose' | 'debug' | 'trace'.
-define(DEFAULT_ACCESS, ?MODULE).
@@ -230,7 +232,7 @@ e_has_var(X, Pos) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Start and stop
--spec start() -> 'ok' | {'error', term()}.
+-spec start() -> result().
start() ->
start([]).
@@ -250,7 +252,7 @@ start_() ->
{error, R}
end.
--spec start([{Option::atom(), Value::_}]) -> 'ok' | {'error', term()}.
+-spec start([{Option::atom(), Value::_}]) -> result().
start(ExtraEnv) when is_list(ExtraEnv) ->
case mnesia_lib:ensure_loaded(?APPLICATION) of
ok ->
@@ -281,8 +283,8 @@ stop() ->
Other -> Other
end.
--spec change_config(Config::config_key(), Value::config_value()) ->
- config_result().
+-spec change_config(Config, Value) -> config_result() when
+ Config :: config_key(), Value :: config_value().
change_config(extra_db_nodes, Ns) when is_list(Ns) ->
mnesia_controller:connect_nodes(Ns);
change_config(dc_dump_limit, N) when is_number(N), N > 0 ->
@@ -299,6 +301,10 @@ change_config(BadKey, _BadVal) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Debugging
+
+-spec set_debug_level(Level :: debug_level()) ->
+ OldLevel :: debug_level().
+
set_debug_level(Level) ->
mnesia_subscr:set_debug_level(Level).
@@ -371,7 +377,7 @@ transaction(Fun) ->
-spec transaction(Fun, Retries) -> t_result(Res) when
Fun :: fun(() -> Res),
Retries :: non_neg_integer() | 'infinity';
- (Fun, [Arg::_]) -> t_result(Res) when
+ (Fun, Args::[Arg::_]) -> t_result(Res) when
Fun :: fun((...) -> Res).
transaction(Fun, Retries) when is_integer(Retries), Retries >= 0 ->
transaction(get(mnesia_activity_state), Fun, [], Retries, ?DEFAULT_ACCESS, async);
@@ -392,9 +398,9 @@ sync_transaction(Fun) ->
transaction(get(mnesia_activity_state), Fun, [], infinity, ?DEFAULT_ACCESS, sync).
-spec sync_transaction(Fun, Retries) -> t_result(Res) when
- Fun :: fun(() -> Res),
+ Fun :: fun(() -> Res) | fun((...) -> Res),
Retries :: non_neg_integer() | 'infinity';
- (Fun, [Arg::_]) -> t_result(Res) when
+ (Fun, Args :: [Arg::_]) -> t_result(Res) when
Fun :: fun((...) -> Res).
sync_transaction(Fun, Retries) when is_integer(Retries), Retries >= 0 ->
transaction(get(mnesia_activity_state), Fun, [], Retries, ?DEFAULT_ACCESS, sync);
@@ -2637,18 +2643,18 @@ load_mnesia_or_abort() ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Database mgt
--spec create_schema(Ns::[node()]) -> 'ok' | {'error', Reason::term()}.
+-spec create_schema(Ns::[node()]) -> result().
create_schema(Ns) ->
create_schema(Ns, []).
--spec create_schema(Ns::[node()], [Prop]) -> 'ok' | {'error', Reason::term()} when
+-spec create_schema(Ns::[node()], [Prop]) -> result() when
Prop :: BackendType | IndexPlugin,
BackendType :: {backend_types, [{Name::atom(), Module::module()}]},
IndexPlugin :: {index_plugins, [{{Name::atom()}, Module::module(), Function::atom()}]}.
create_schema(Ns, Properties) ->
mnesia_bup:create_schema(Ns, Properties).
--spec delete_schema(Ns::[node()]) -> 'ok' | {'error', Reason::term()}.
+-spec delete_schema(Ns::[node()]) -> result().
delete_schema(Ns) ->
mnesia_schema:delete_schema(Ns).
@@ -2656,12 +2662,12 @@ delete_schema(Ns) ->
add_backend_type(Alias, Module) ->
mnesia_schema:add_backend_type(Alias, Module).
--spec backup(Dest::term()) -> 'ok' | {'error', Reason::term()}.
+-spec backup(Dest::term()) -> result().
backup(Opaque) ->
mnesia_log:backup(Opaque).
-spec backup(Dest::term(), Mod::module()) ->
- 'ok' | {'error', Reason::term()}.
+ result().
backup(Opaque, Mod) ->
mnesia_log:backup(Opaque, Mod).
@@ -2679,12 +2685,12 @@ traverse_backup(S, T, Fun, Acc) ->
traverse_backup(S, SM, T, TM, F, A) ->
mnesia_bup:traverse_backup(S, SM, T, TM, F, A).
--spec install_fallback(Src::term()) -> 'ok' | {'error', Reason::term()}.
+-spec install_fallback(Src::term()) -> result().
install_fallback(Opaque) ->
mnesia_bup:install_fallback(Opaque).
-spec install_fallback(Src::term(), Mod::module()|[Opt]) ->
- 'ok' | {'error', Reason::term()} when
+ result() when
Opt :: Module | Scope | Dir,
Module :: {'module', Mod::module()},
Scope :: {'scope', 'global' | 'local'},
@@ -2692,11 +2698,11 @@ install_fallback(Opaque) ->
install_fallback(Opaque, Mod) ->
mnesia_bup:install_fallback(Opaque, Mod).
--spec uninstall_fallback() -> 'ok' | {'error', Reason::term()}.
+-spec uninstall_fallback() -> result().
uninstall_fallback() ->
mnesia_bup:uninstall_fallback().
--spec uninstall_fallback(Args) -> 'ok' | {'error', Reason::term()} when
+-spec uninstall_fallback(Args) -> result() when
Args :: [{'mnesia_dir', Dir::string()}].
uninstall_fallback(Args) ->
mnesia_bup:uninstall_fallback(Args).
@@ -2707,16 +2713,17 @@ uninstall_fallback(Args) ->
activate_checkpoint(Args) ->
mnesia_checkpoint:activate(Args).
--spec deactivate_checkpoint(Name::_) -> 'ok' | {'error', Reason::term()}.
+-spec deactivate_checkpoint(Name::_) -> result().
deactivate_checkpoint(Name) ->
mnesia_checkpoint:deactivate(Name).
--spec backup_checkpoint(Name::_, Dest::_) -> 'ok' | {'error', Reason::term()}.
+-spec backup_checkpoint(Name, Dest) -> result() when
+ Name :: term(), Dest :: term().
backup_checkpoint(Name, Opaque) ->
mnesia_log:backup_checkpoint(Name, Opaque).
--spec backup_checkpoint(Name::_, Dest::_, Mod::module()) ->
- 'ok' | {'error', Reason::term()}.
+-spec backup_checkpoint(Name, Dest, Mod) -> result() when
+ Name :: term(), Dest :: term(), Mod :: module().
backup_checkpoint(Name, Opaque, Mod) ->
mnesia_log:backup_checkpoint(Name, Opaque, Mod).
@@ -2743,7 +2750,8 @@ create_table(Name, Arg) ->
delete_table(Tab) ->
mnesia_schema:delete_table(Tab).
--spec add_table_copy(Tab::table(), N::node(), ST::storage_type()) -> t_result(ok).
+-spec add_table_copy(Tab, N, ST) -> t_result(ok) when
+ Tab :: table(), N::node(), ST::storage_type().
add_table_copy(Tab, N, S) ->
mnesia_schema:add_table_copy(Tab, N, S).
@@ -2755,10 +2763,12 @@ del_table_copy(Tab, N) ->
move_table_copy(Tab, From, To) ->
mnesia_schema:move_table(Tab, From, To).
--spec add_table_index(Tab::table(), I::index_attr()) -> t_result(ok).
+-spec add_table_index(Tab, I) -> t_result(ok) when
+ Tab :: table(), I :: index_attr().
add_table_index(Tab, Ix) ->
mnesia_schema:add_table_index(Tab, Ix).
--spec del_table_index(Tab::table(), I::index_attr()) -> t_result(ok).
+-spec del_table_index(Tab, I) -> t_result(ok) when
+ Tab::table(), I::index_attr().
del_table_index(Tab, Ix) ->
mnesia_schema:del_table_index(Tab, Ix).
@@ -2842,7 +2852,7 @@ dump_tables(Tabs) ->
%% allow the user to wait for some tables to be loaded
-spec wait_for_tables([Tab::table()], TMO::timeout()) ->
- 'ok' | {'timeout', [table()]} | {'error', Reason::term()}.
+ result() | {'timeout', [table()]}.
wait_for_tables(Tabs, Timeout) ->
mnesia_controller:wait_for_tables(Tabs, Timeout).
@@ -2867,7 +2877,7 @@ change_table_load_order(T, O) ->
change_table_majority(T, M) ->
mnesia_schema:change_table_majority(T, M).
--spec set_master_nodes(Ns::[node()]) -> 'ok' | {'error', Reason::term()}.
+-spec set_master_nodes(Ns::[node()]) -> result().
set_master_nodes(Nodes) when is_list(Nodes) ->
UseDir = system_info(use_dir),
IsRunning = system_info(is_running),
@@ -2906,8 +2916,7 @@ log_valid_master_nodes(Cstructs, Nodes, UseDir, IsRunning) ->
Args = lists:map(Fun, Cstructs),
mnesia_recover:log_master_nodes(Args, UseDir, IsRunning).
--spec set_master_nodes(Tab::table(), Ns::[node()]) ->
- 'ok' | {'error', Reason::term()}.
+-spec set_master_nodes(Tab::table(), Ns::[node()]) -> result().
set_master_nodes(Tab, Nodes) when is_list(Nodes) ->
UseDir = system_info(use_dir),
IsRunning = system_info(is_running),
@@ -2962,7 +2971,7 @@ set_master_nodes(Tab, Nodes) ->
dump_log() ->
mnesia_controller:sync_dump_log(user).
--spec sync_log() -> 'ok' | {'error', Reason::term()}.
+-spec sync_log() -> result().
sync_log() ->
mnesia_monitor:sync_log(latest_log).
@@ -3136,7 +3145,7 @@ snmp_filter_key(undefined, RowIndex, Tab, Store) ->
load_textfile(F) ->
mnesia_text:load_textfile(F).
--spec dump_to_textfile(File :: file:filename()) -> 'ok' | 'error' | {'error', term()}.
+-spec dump_to_textfile(File :: file:filename()) -> result() | 'error'.
dump_to_textfile(F) ->
mnesia_text:dump_to_textfile(F).
diff --git a/lib/mnesia/src/mnesia_recover.erl b/lib/mnesia/src/mnesia_recover.erl
index 2ccea1ea6d..8749b625a1 100644
--- a/lib/mnesia/src/mnesia_recover.erl
+++ b/lib/mnesia/src/mnesia_recover.erl
@@ -449,6 +449,9 @@ wait_for_decision(D, InitBy, N) ->
if
Outcome =:= committed -> {Tid, committed};
Outcome =:= aborted -> {Tid, aborted};
+ InitBy == startup ->
+ {ok, Res} = call({wait_for_decision, D}),
+ {Tid, Res};
Outcome =:= presume_abort ->
case N > Max of
true -> {Tid, aborted};
@@ -460,10 +463,7 @@ wait_for_decision(D, InitBy, N) ->
%% Wait a while for active transactions
%% to end and try again
timer:sleep(100),
- wait_for_decision(D, InitBy, N);
- InitBy == startup ->
- {ok, Res} = call({wait_for_decision, D}),
- {Tid, Res}
+ wait_for_decision(D, InitBy, N)
end.
still_pending([Tid | Pending]) ->
diff --git a/lib/mnesia/vsn.mk b/lib/mnesia/vsn.mk
index ae849f2771..ba006208cd 100644
--- a/lib/mnesia/vsn.mk
+++ b/lib/mnesia/vsn.mk
@@ -1 +1 @@
-MNESIA_VSN = 4.16.3
+MNESIA_VSN = 4.17
diff --git a/lib/observer/doc/src/notes.xml b/lib/observer/doc/src/notes.xml
index b27de66984..2533f99bd5 100644
--- a/lib/observer/doc/src/notes.xml
+++ b/lib/observer/doc/src/notes.xml
@@ -32,6 +32,21 @@
<p>This document describes the changes made to the Observer
application.</p>
+<section><title>Observer 2.9.4</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Minor updates due to the new spawn improvements made.</p>
+ <p>
+ Own Id: OTP-16368 Aux Id: OTP-15251 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Observer 2.9.3</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/observer/src/observer.app.src b/lib/observer/src/observer.app.src
index 55de6de0c6..ca65ee703e 100644
--- a/lib/observer/src/observer.app.src
+++ b/lib/observer/src/observer.app.src
@@ -66,7 +66,7 @@
{registered, []},
{applications, [kernel, stdlib]},
{env, []},
- {runtime_dependencies, ["wx-1.2","stdlib-@OTP-15251@","runtime_tools-1.8.14",
- "kernel-@OTP-15251@","et-1.5","erts-@OTP-15251@"]}]}.
+ {runtime_dependencies, ["wx-1.2","stdlib-3.13","runtime_tools-1.8.14",
+ "kernel-7.0","et-1.5","erts-11.0"]}]}.
diff --git a/lib/observer/src/observer_alloc_wx.erl b/lib/observer/src/observer_alloc_wx.erl
index 1b144e05dc..a7b55f0f72 100644
--- a/lib/observer/src/observer_alloc_wx.erl
+++ b/lib/observer/src/observer_alloc_wx.erl
@@ -158,7 +158,7 @@ handle_info({refresh, Seq},
State#state.active andalso (catch wxWindow:refresh(Panel)),
erlang:send_after(1000 div ?DISP_FREQ, self(), {refresh, Next}),
if Seq =:= (trunc(DispF)-1) ->
- Req = rpc:async_call(Node, observer_backend, sys_info, []),
+ Req = request_info(Node),
{noreply, State#state{time=Ti#ti{tick=Next}, async=Req}};
true ->
{noreply, State#state{time=Ti#ti{tick=Next}}}
@@ -193,6 +193,13 @@ code_change(_, _, State) ->
%%%%%%%%%%
+request_info(Node) ->
+ ReplyTo = self(),
+ spawn(fun() ->
+ Res = rpc:call(Node, observer_backend, sys_info, []),
+ ReplyTo ! {self(), {promise_reply, Res}}
+ end).
+
restart_fetcher(Node, #state{panel=Panel, wins=Wins0, time=Ti} = State) ->
case rpc:call(Node, observer_backend, sys_info, []) of
{badrpc, _} -> State;
diff --git a/lib/observer/vsn.mk b/lib/observer/vsn.mk
index 6b733687b8..4de0cc113f 100644
--- a/lib/observer/vsn.mk
+++ b/lib/observer/vsn.mk
@@ -1 +1 @@
-OBSERVER_VSN = 2.9.3
+OBSERVER_VSN = 2.9.4
diff --git a/lib/odbc/c_src/odbcserver.c b/lib/odbc/c_src/odbcserver.c
index fb4f61417e..996c122199 100644
--- a/lib/odbc/c_src/odbcserver.c
+++ b/lib/odbc/c_src/odbcserver.c
@@ -304,7 +304,7 @@ int main(void)
static void spawn_sup(const char *port)
{
DWORD threadId;
- (HANDLE)_beginthreadex(NULL, 0, supervise, port, 0, &threadId);
+ _beginthreadex(NULL, 0, supervise, port, 0, &threadId);
}
#elif defined(UNIX)
static void spawn_sup(const char *port)
diff --git a/lib/odbc/c_src/odbcserver.h b/lib/odbc/c_src/odbcserver.h
index 0461c57d1f..28bb2b9030 100644
--- a/lib/odbc/c_src/odbcserver.h
+++ b/lib/odbc/c_src/odbcserver.h
@@ -120,7 +120,7 @@
/*------------------------ TYPDEFS ----------------------------------*/
-typedef char byte;
+typedef unsigned char byte;
typedef int Boolean;
typedef struct {
diff --git a/lib/odbc/doc/src/notes.xml b/lib/odbc/doc/src/notes.xml
index 8d708162e4..d8c6126496 100644
--- a/lib/odbc/doc/src/notes.xml
+++ b/lib/odbc/doc/src/notes.xml
@@ -32,7 +32,35 @@
<p>This document describes the changes made to the odbc application.
</p>
- <section><title>ODBC 2.12.4</title>
+ <section><title>ODBC 2.13</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix various compiler warnings on 64-bit Windows.</p>
+ <p>
+ Own Id: OTP-15800</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Rewrite due to the removal of <c>erl_interface</c> legacy
+ functions.</p>
+ <p>
+ Own Id: OTP-16544 Aux Id: OTP-16328 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>ODBC 2.12.4</title>
<section><title>Improvements and New Features</title>
<list>
diff --git a/lib/odbc/vsn.mk b/lib/odbc/vsn.mk
index df6db09f2f..cf271f3505 100644
--- a/lib/odbc/vsn.mk
+++ b/lib/odbc/vsn.mk
@@ -1 +1 @@
-ODBC_VSN = 2.12.4
+ODBC_VSN = 2.13
diff --git a/lib/os_mon/c_src/nteventlog/elog_main.c b/lib/os_mon/c_src/nteventlog/elog_main.c
index c31e3ef1ff..f1baaa4084 100644
--- a/lib/os_mon/c_src/nteventlog/elog_main.c
+++ b/lib/os_mon/c_src/nteventlog/elog_main.c
@@ -266,11 +266,11 @@ BOOL output_record(char *category, EVENTLOGRECORD *event){
strlen(PIPE_LOG_FORMAT) +
PIPE_LOG_EXTRA);
sprintf(buff,PIPE_LOG_FORMAT,
- strlen(tbuff),tbuff,
- strlen(category), category,
- strlen(fac), fac,
- strlen(sev), sev,
- strlen(bigbuff), bigbuff);
+ (int)strlen(tbuff), tbuff,
+ (int)strlen(category), category,
+ (int)strlen(fac), fac,
+ (int)strlen(sev), sev,
+ (int)strlen(bigbuff), bigbuff);
ret = data_to_pipe(buff,ackbuff, ACK_MAX);
if(ret && strcmp(ackbuff,PIPE_LOG_ACK))
ret = FALSE;
diff --git a/lib/os_mon/doc/src/notes.xml b/lib/os_mon/doc/src/notes.xml
index 63efa96e2f..b08ade9938 100644
--- a/lib/os_mon/doc/src/notes.xml
+++ b/lib/os_mon/doc/src/notes.xml
@@ -31,6 +31,33 @@
</header>
<p>This document describes the changes made to the OS_Mon application.</p>
+<section><title>Os_Mon 2.5.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix various compiler warnings on 64-bit Windows.</p>
+ <p>
+ Own Id: OTP-15800</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>Refactored the internal handling of deprecated and
+ removed functions.</p>
+ <p>
+ Own Id: OTP-16469</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Os_Mon 2.5.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/os_mon/vsn.mk b/lib/os_mon/vsn.mk
index 6081e181ff..e4b7574e02 100644
--- a/lib/os_mon/vsn.mk
+++ b/lib/os_mon/vsn.mk
@@ -1 +1 @@
-OS_MON_VSN = 2.5.1
+OS_MON_VSN = 2.5.2
diff --git a/lib/parsetools/doc/src/notes.xml b/lib/parsetools/doc/src/notes.xml
index f8cd9b972d..3975c55c6c 100644
--- a/lib/parsetools/doc/src/notes.xml
+++ b/lib/parsetools/doc/src/notes.xml
@@ -31,6 +31,22 @@
</header>
<p>This document describes the changes made to the Parsetools application.</p>
+<section><title>Parsetools 2.2</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Remove usage and documentation of old requests of the
+ I/O-protocol.</p>
+ <p>
+ Own Id: OTP-15695</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Parsetools 2.1.8</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/parsetools/vsn.mk b/lib/parsetools/vsn.mk
index 1a5201ce5d..c18fcbe762 100644
--- a/lib/parsetools/vsn.mk
+++ b/lib/parsetools/vsn.mk
@@ -1 +1 @@
-PARSETOOLS_VSN = 2.1.8
+PARSETOOLS_VSN = 2.2
diff --git a/lib/public_key/asn1/OTP-PKIX.asn1 b/lib/public_key/asn1/OTP-PKIX.asn1
index ff3250b383..e1d8f2e121 100644
--- a/lib/public_key/asn1/OTP-PKIX.asn1
+++ b/lib/public_key/asn1/OTP-PKIX.asn1
@@ -122,7 +122,9 @@ IMPORTS
sha224WithRSAEncryption,
sha256WithRSAEncryption,
sha384WithRSAEncryption,
- sha512WithRSAEncryption
+ sha512WithRSAEncryption,
+ id-RSASSA-PSS,
+ RSASSA-PSS-params
FROM PKCS-1 {
iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-1(1)
@@ -341,6 +343,7 @@ SupportedSignatureAlgorithms SIGNATURE-ALGORITHM-CLASS ::= {
sha256-with-rsa-encryption |
sha384-with-rsa-encryption |
sha512-with-rsa-encryption |
+ rsassa-pss |
ecdsa-with-sha1 |
ecdsa-with-sha224 |
ecdsa-with-sha256 |
@@ -348,7 +351,7 @@ SupportedSignatureAlgorithms SIGNATURE-ALGORITHM-CLASS ::= {
ecdsa-with-sha512 }
SupportedPublicKeyAlgorithms PUBLIC-KEY-ALGORITHM-CLASS ::= {
- dsa | rsa-encryption | dh | kea | ec-public-key }
+ dsa | rsa-encryption | rsa-pss | dh | kea | ec-public-key }
-- DSA Keys and Signatures
@@ -430,6 +433,11 @@ SupportedPublicKeyAlgorithms PUBLIC-KEY-ALGORITHM-CLASS ::= {
ID sha512WithRSAEncryption
TYPE NULL }
+ rsassa-pss SIGNATURE-ALGORITHM-CLASS ::= {
+ ID id-RSASSA-PSS
+ TYPE RSASSA-PSS-params }
+
+
-- Certificate.signature
-- See PKCS #1 (RFC 2313). XXX
@@ -440,6 +448,11 @@ SupportedPublicKeyAlgorithms PUBLIC-KEY-ALGORITHM-CLASS ::= {
TYPE NULL
PUBLIC-KEY-TYPE RSAPublicKey }
+ rsa-pss PUBLIC-KEY-ALGORITHM-CLASS ::= {
+ ID id-RSASSA-PSS
+ TYPE RSASSA-PSS-params
+ PUBLIC-KEY-TYPE RSAPublicKey }
+
--
-- Diffie-Hellman Keys
--
@@ -494,6 +507,7 @@ SupportedPublicKeyAlgorithms PUBLIC-KEY-ALGORITHM-CLASS ::= {
ID ecdsa-with-SHA512
TYPE EcpkParameters } -- XXX Must be empty and not NULL
+
FIELD-ID-CLASS ::= CLASS {
&id OBJECT IDENTIFIER UNIQUE,
&Type }
diff --git a/lib/public_key/asn1/PKCS-1.asn1 b/lib/public_key/asn1/PKCS-1.asn1
index 117eacd8ad..6fb7ccb981 100644
--- a/lib/public_key/asn1/PKCS-1.asn1
+++ b/lib/public_key/asn1/PKCS-1.asn1
@@ -1,10 +1,15 @@
+-- PKCS #1 v2.2 ASN.1 Module
+-- Revised October 27, 2012
+-- (plain merged with previous version to support all that we need)
+
+-- This module has been checked for conformance with the
+-- ASN.1 standard by the OSS ASN.1 Tools
+
PKCS-1 {
iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-1(1)
modules(0) pkcs-1(1)
}
--- $Revision: 1.1 $
-
DEFINITIONS EXPLICIT TAGS ::=
BEGIN
@@ -15,43 +20,74 @@ BEGIN
-- gov(101) csor(3) nistalgorithm(4) modules(0) sha2(1)
-- };
+-- ============================
+-- Basic object identifiers
+-- ============================
+-- The DER encoding of this in hexadecimal is:
+-- (0x)06 08
+-- 2A 86 48 86 F7 0D 01 01
+
pkcs-1 OBJECT IDENTIFIER ::= {
iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 1
}
+--
+-- When rsaEncryption is used in an AlgorithmIdentifier the parameters
+-- MUST be present and MUST be NULL.
+--
rsaEncryption OBJECT IDENTIFIER ::= { pkcs-1 1 }
+--
+-- When id-RSAES-OAEP is used in an AlgorithmIdentifier the parameters MUST
+-- be present and MUST be RSAES-OAEP-params.
+--
id-RSAES-OAEP OBJECT IDENTIFIER ::= { pkcs-1 7 }
-id-pSpecified OBJECT IDENTIFIER ::= { pkcs-1 9 }
+--
+-- When id-pSpecified is used in an AlgorithmIdentifier the parameters MUST
+-- be an OCTET STRING.
+--
+id-pSpecified OBJECT IDENTIFIER ::= { pkcs-1 9 }
+--
+-- When id-RSASSA-PSS is used in an AlgorithmIdentifier the parameters MUST
+-- be present and MUST be RSASSA-PSS-params.
+--
id-RSASSA-PSS OBJECT IDENTIFIER ::= { pkcs-1 10 }
-md2WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 2 }
-md5WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 4 }
-sha1WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 5 }
-sha256WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 11 }
-sha384WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 12 }
-sha512WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 13 }
-sha224WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 14 }
+--
+-- When the following OIDs are used in an AlgorithmIdentifier the parameters
+-- MUST be present and MUST be NULL.
+--
+md2WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 2 }
+md5WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 4 }
+sha1WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 5 }
+sha224WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 14 }
+sha256WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 11 }
+sha384WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 12 }
+sha512WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 13 }
+sha512-224WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 15 }
+sha512-256WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 16 }
-- ISO oid - equvivalent to sha1WithRSAEncryption
sha-1WithRSAEncryption OBJECT IDENTIFIER ::= {
iso(1) identified-organization(3) oiw(14) secsig(3) algorithms(2) sha-1WithRSAEncryption(29)}
-
-id-sha1 OBJECT IDENTIFIER ::= {
- iso(1) identified-organization(3) oiw(14) secsig(3)
- algorithms(2) 26
+--
+-- This OID really belongs in a module with the secsig OIDs.
+--
+id-sha1 OBJECT IDENTIFIER ::= {
+ iso(1) identified-organization(3) oiw(14) secsig(3) algorithms(2) 26
}
-
+--
+-- OIDs for MD2 and MD5, allowed only in EMSA-PKCS1-v1_5.
+--
id-md2 OBJECT IDENTIFIER ::= {
- iso(1) member-body(2) us(840) rsadsi(113549) digestAlgorithm(2) 2
-}
+ iso(1) member-body(2) us(840) rsadsi(113549) digestAlgorithm(2) 2}
id-md5 OBJECT IDENTIFIER ::= {
- iso(1) member-body(2) us(840) rsadsi(113549) digestAlgorithm(2) 5
-}
+ iso(1) member-body(2) us(840) rsadsi(113549) digestAlgorithm(2) 5}
+-- Additional oids that where included previously...
id-hmacWithSHA224 OBJECT IDENTIFIER ::= {
iso(1) member-body(2) us(840) rsadsi(113549) digestAlgorithm(2) 8
}
@@ -68,8 +104,6 @@ id-hmacWithSHA512 OBJECT IDENTIFIER ::= {
iso(1) member-body(2) us(840) rsadsi(113549) digestAlgorithm(2) 11
}
-id-mgf1 OBJECT IDENTIFIER ::= { pkcs-1 8 }
-
id-sha224 OBJECT IDENTIFIER ::= { joint-iso-itu-t(2)
country(16) us(840) organization(1) gov(101) csor(3)
nistalgorithm(4) hashalgs(2) 4 }
@@ -86,68 +120,255 @@ id-sha512 OBJECT IDENTIFIER ::= { joint-iso-itu-t(2)
country(16) us(840) organization(1) gov(101) csor(3)
nistalgorithm(4) hashalgs(2) 3 }
+--
+-- When id-mgf1 is used in an AlgorithmIdentifier the parameters MUST be
+-- present and MUST be a HashAlgorithm, for example sha1.
+--
+id-mgf1 OBJECT IDENTIFIER ::= { pkcs-1 8 }
+
+-- ================
+-- Useful types
+-- ================
+ALGORITHM-IDENTIFIER ::= CLASS {
+ &id OBJECT IDENTIFIER UNIQUE,
+ &Type OPTIONAL
+}
-RSAPublicKey ::= SEQUENCE {
- modulus INTEGER, -- n
- publicExponent INTEGER -- e
+ WITH SYNTAX { OID &id [PARAMETERS &Type] }
+-- Note: the parameter InfoObjectSet in the following definitions allows a
+-- distinct information object set to be specified for sets of algorithms
+-- such as:
+-- DigestAlgorithms ALGORITHM-IDENTIFIER ::= {
+-- { OID id-md2 PARAMETERS NULL }|
+-- { OID id-md5 PARAMETERS NULL }|
+-- { OID id-sha1 PARAMETERS NULL }
+-- }
+--
+AlgorithmIdentifier-PKCS1 { ALGORITHM-IDENTIFIER:InfoObjectSet } ::= SEQUENCE {
+ algorithm
+ ALGORITHM-IDENTIFIER.&id({InfoObjectSet}),
+ parameters
+ ALGORITHM-IDENTIFIER.&Type({InfoObjectSet}{@.algorithm}) OPTIONAL
+}
+
+-- ==============
+-- Algorithms
+-- ==============
+--
+-- Allowed EME-OAEP and EMSA-PSS digest algorithms.
+--
+OAEP-PSSDigestAlgorithms ALGORITHM-IDENTIFIER ::= {
+ { OID id-sha1 PARAMETERS NULL }|
+ { OID id-sha256 PARAMETERS NULL }|
+ { OID id-sha384 PARAMETERS NULL }|
+ { OID id-sha512 PARAMETERS NULL },
+ ... -- Allows for future expansion --
+}
+--
+-- Allowed EMSA-PKCS1-v1_5 digest algorithms.
+--
+PKCS1-v1-5DigestAlgorithms ALGORITHM-IDENTIFIER ::= {
+ { OID id-md2 PARAMETERS NULL }|
+ { OID id-md5 PARAMETERS NULL }|
+ { OID id-sha1 PARAMETERS NULL }|
+ { OID id-sha256 PARAMETERS NULL }|
+ { OID id-sha384 PARAMETERS NULL }|
+ { OID id-sha512 PARAMETERS NULL }
+}
+
+-- When id-md2 and id-md5 are used in an AlgorithmIdentifier, the
+-- parameters field shall have a value of type NULL.
+
+-- When id-sha1, id-sha224, id-sha256, id-sha384, id-sha512,
+-- id-sha512-224, and id-sha512-256 are used in an
+-- AlgorithmIdentifier, the parameters (which are optional) SHOULD be
+-- omitted, but if present, they SHALL have a value of type NULL.
+-- However, implementations MUST accept AlgorithmIdentifier values
+-- both without parameters and with NULL parameters.
+
+-- Exception: When formatting the DigestInfoValue in EMSA-PKCS1-v1_5
+-- (see Section 9.2), the parameters field associated with id-sha1,
+-- id-sha224, id-sha256, id-sha384, id-sha512, id-sha512-224, and
+-- id-sha512-256 SHALL have a value of type NULL. This is to
+-- maintain compatibility with existing implementations and with the
+-- numeric information values already published for EMSA-PKCS1-v1_5,
+-- which are also reflected in IEEE 1363a.
+
+sha1 HashAlgorithm ::= {
+ algorithm id-sha1,
+ parameters SHA1Parameters : NULL
+}
+
+HashAlgorithm ::= AlgorithmIdentifier-PKCS1 { {OAEP-PSSDigestAlgorithms} }
+SHA1Parameters ::= NULL
+
+--
+-- Allowed mask generation function algorithms.
+-- If the identifier is id-mgf1, the parameters are a HashAlgorithm.
+--
+PKCS1MGFAlgorithms ALGORITHM-IDENTIFIER ::= {
+ { OID id-mgf1 PARAMETERS HashAlgorithm },
+ ... -- Allows for future expansion --
+}
+
+--
+-- Default AlgorithmIdentifier for id-RSAES-OAEP.maskGenAlgorithm and
+-- id-RSASSA-PSS.maskGenAlgorithm.
+--
+mgf1SHA1 MaskGenAlgorithm ::= {
+ algorithm id-mgf1,
+ parameters HashAlgorithm : sha1
}
+MaskGenAlgorithm ::= AlgorithmIdentifier-PKCS1 { {PKCS1MGFAlgorithms} }
+
+--
+-- Allowed algorithms for pSourceAlgorithm.
+--
+PKCS1PSourceAlgorithms ALGORITHM-IDENTIFIER ::= {
+ { OID id-pSpecified PARAMETERS EncodingParameters },
+ ... -- Allows for future expansion --
+}
+
+EncodingParameters ::= OCTET STRING(SIZE(0..MAX))
+
+--
+-- This identifier means that the label L is an empty string, so the digest
+-- of the empty string appears in the RSA block before masking.
+--
+pSpecifiedEmpty PSourceAlgorithm ::= {
+ algorithm id-pSpecified,
+ parameters EncodingParameters : emptyString
+}
+PSourceAlgorithm ::= AlgorithmIdentifier-PKCS1 { {PKCS1PSourceAlgorithms} }
+
+emptyString EncodingParameters ::= ''H
+
+--
+-- Type identifier definitions for the PKCS #1 OIDs.
+--
+PKCS1Algorithms ALGORITHM-IDENTIFIER ::= {
+ { OID rsaEncryption PARAMETERS NULL } |
+ { OID md2WithRSAEncryption PARAMETERS NULL } |
+ { OID md5WithRSAEncryption PARAMETERS NULL } |
+ { OID sha1WithRSAEncryption PARAMETERS NULL } |
+ { OID sha256WithRSAEncryption PARAMETERS NULL } |
+ { OID sha384WithRSAEncryption PARAMETERS NULL } |
+ { OID sha512WithRSAEncryption PARAMETERS NULL } |
+ { OID id-RSAES-OAEP PARAMETERS RSAES-OAEP-params } |
+ PKCS1PSourceAlgorithms |
+ { OID id-RSASSA-PSS PARAMETERS RSASSA-PSS-params } ,
+ ... -- Allows for future expansion --
+}
+-- ===================
+-- Main structures
+-- ===================
+RSAPublicKey ::= SEQUENCE {
+ modulus INTEGER, -- n
+ publicExponent INTEGER -- e
+}
+--
+-- Representation of RSA private key with information for the CRT algorithm.
+--
RSAPrivateKey ::= SEQUENCE {
- version Version,
- modulus INTEGER, -- n
- publicExponent INTEGER, -- e
- privateExponent INTEGER, -- d
- prime1 INTEGER, -- p
- prime2 INTEGER, -- q
- exponent1 INTEGER, -- d mod (p-1)
- exponent2 INTEGER, -- d mod (q-1)
- coefficient INTEGER, -- (inverse of q) mod p
- otherPrimeInfos OtherPrimeInfos OPTIONAL
+ version Version,
+ modulus INTEGER, -- n
+ publicExponent INTEGER, -- e
+ privateExponent INTEGER, -- d
+ prime1 INTEGER, -- p
+ prime2 INTEGER, -- q
+ exponent1 INTEGER, -- d mod (p-1)
+ exponent2 INTEGER, -- d mod (q-1)
+ coefficient INTEGER, -- (inverse of q) mod p
+ otherPrimeInfos OtherPrimeInfos OPTIONAL
}
Version ::= INTEGER { two-prime(0), multi(1) }
- (CONSTRAINED BY {
- -- version must be multi if otherPrimeInfos present --
- })
+ (CONSTRAINED BY {-- version must be multi if otherPrimeInfos present --})
OtherPrimeInfos ::= SEQUENCE SIZE(1..MAX) OF OtherPrimeInfo
OtherPrimeInfo ::= SEQUENCE {
- prime INTEGER, -- ri
- exponent INTEGER, -- di
- coefficient INTEGER -- ti
+ prime INTEGER, -- ri
+ exponent INTEGER, -- di
+ coefficient INTEGER -- ti
}
-Algorithm ::= SEQUENCE {
- algorithm OBJECT IDENTIFIER,
- parameters ANY DEFINED BY algorithm OPTIONAL
+--
+-- AlgorithmIdentifier.parameters for id-RSAES-OAEP.
+-- Note that the tags in this Sequence are explicit.
+--
+
+RSAES-OAEP-params ::= SEQUENCE {
+ hashAlgorithm [0] HashAlgorithm DEFAULT sha1,
+ maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT mgf1SHA1,
+ pSourceAlgorithm [2] PSourceAlgorithm DEFAULT pSpecifiedEmpty
}
-AlgorithmNull ::= SEQUENCE {
- algorithm OBJECT IDENTIFIER,
- parameters NULL
+--
+-- Identifier for default RSAES-OAEP algorithm identifier.
+-- The DER Encoding of this is in hexadecimal:
+-- (0x)30 0D
+-- 06 09
+-- 2A 86 48 86 F7 0D 01 01 07
+-- 30 00
+-- Notice that the DER encoding of default values is "empty".
+--
+rSAES-OAEP-Default-Identifier RSAES-AlgorithmIdentifier ::= {
+ algorithm id-RSAES-OAEP,
+ parameters RSAES-OAEP-params : {
+ hashAlgorithm sha1,
+ maskGenAlgorithm mgf1SHA1,
+ pSourceAlgorithm pSpecifiedEmpty
+ }
}
+RSAES-AlgorithmIdentifier ::= AlgorithmIdentifier-PKCS1 {
+ {PKCS1Algorithms}
+}
+--
+-- AlgorithmIdentifier.parameters for id-RSASSA-PSS.
+-- Note that the tags in this Sequence are explicit.
+--
RSASSA-PSS-params ::= SEQUENCE {
- hashAlgorithm [0] Algorithm, -- DEFAULT sha1,
- maskGenAlgorithm [1] Algorithm, -- DEFAULT mgf1SHA1,
- saltLength [2] INTEGER DEFAULT 20,
- trailerField [3] TrailerField DEFAULT trailerFieldBC
+ hashAlgorithm [0] HashAlgorithm DEFAULT sha1,
+ maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT mgf1SHA1,
+ saltLength [2] INTEGER DEFAULT 20,
+ trailerField [3] TrailerField DEFAULT trailerFieldBC
}
TrailerField ::= INTEGER { trailerFieldBC(1) }
-DigestInfo ::= SEQUENCE {
- digestAlgorithm Algorithm,
- digest OCTET STRING
+--
+-- Identifier for default RSASSA-PSS algorithm identifier
+-- The DER Encoding of this is in hexadecimal:
+-- (0x)30 0D
+-- 06 09
+-- 2A 86 48 86 F7 0D 01 01 0A
+-- 30 00
+-- Notice that the DER encoding of default values is "empty".
+--
+rSASSA-PSS-Default-Identifier RSASSA-AlgorithmIdentifier ::= {
+ algorithm id-RSASSA-PSS,
+ parameters RSASSA-PSS-params : {
+ hashAlgorithm sha1,
+ maskGenAlgorithm mgf1SHA1,
+ saltLength 20,trailerField trailerFieldBC
+ }
+}
+RSASSA-AlgorithmIdentifier ::= AlgorithmIdentifier-PKCS1 {
+ {PKCS1Algorithms}
}
-DigestInfoNull ::= SEQUENCE {
- digestAlgorithm AlgorithmNull,
+--
+-- Syntax for the EMSA-PKCS1-v1_5 hash identifier.
+--
+DigestInfo ::= SEQUENCE {
+ digestAlgorithm DigestAlgorithm,
digest OCTET STRING
}
+DigestAlgorithm ::= AlgorithmIdentifier-PKCS1 { {PKCS1-v1-5DigestAlgorithms} }
-END -- PKCS1Definitions
-
+END -- PKCS1Definitions
diff --git a/lib/public_key/doc/src/notes.xml b/lib/public_key/doc/src/notes.xml
index c182a28c53..b15e745252 100644
--- a/lib/public_key/doc/src/notes.xml
+++ b/lib/public_key/doc/src/notes.xml
@@ -35,6 +35,30 @@
<file>notes.xml</file>
</header>
+<section><title>Public_Key 1.8</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Added support for RSA-PSS signature schemes</p>
+ <p>
+ Own Id: OTP-15247</p>
+ </item>
+ <item>
+ <p>
+ Calls of deprecated functions in the <seeguide
+ marker="crypto:new_api#the-old-api">Old Crypto
+ API</seeguide> are replaced by calls of their <seeguide
+ marker="crypto:new_api#the-new-api">substitutions</seeguide>.</p>
+ <p>
+ Own Id: OTP-16346</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Public_Key 1.7.2</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/public_key/doc/src/public_key.xml b/lib/public_key/doc/src/public_key.xml
index 9c5aaa9812..3e72f88894 100644
--- a/lib/public_key/doc/src/public_key.xml
+++ b/lib/public_key/doc/src/public_key.xml
@@ -100,6 +100,7 @@
<datatype>
<name name="public_key"/>
<name name="rsa_public_key"/>
+ <name name="rsa_pss_public_key"/>
<name name="dsa_public_key"/>
<name name="ec_public_key"/>
<name name="ecpk_parameters"/>
@@ -118,6 +119,7 @@
<datatype>
<name name="private_key"/>
<name name="rsa_private_key"/>
+ <name name="rsa_pss_private_key"/>
<name name="dsa_private_key"/>
<name name="ec_private_key"/>
<desc>
@@ -405,8 +407,8 @@
<v>CertChain = [der_encoded()]</v>
<d>A list of DER-encoded certificates in trust order ending with the peer certificate.</d>
<v>Options = proplists:proplist()</v>
- <v>PublicKeyInfo = {?'rsaEncryption' | ?'id-dsa',
- rsa_public_key() | integer(), 'NULL' | 'Dss-Parms'{}}</v>
+ <v>PublicKeyInfo = {?'rsaEncryption' | ?'id-RSASSA-PSS'| ?'id-dsa',
+ rsa_public_key() | integer(), 'NULL' | 'RSASSA-PSS-params'{} | 'Dss-Parms'{}}</v>
<v>PolicyTree = term()</v>
<d>At the moment this is always an empty list as policies are not currently supported.</d>
<v>Reason = cert_expired | invalid_issuer | invalid_signature | name_not_permitted |
@@ -582,7 +584,15 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
<p> Extracts distribution points from the certificates extensions.</p>
</desc>
</func>
-
+
+ <func>
+ <name name="pkix_hash_type" arity="1" since="@master@"/>
+ <fsummary>Translates OID to Erlang digest type</fsummary>
+ <desc>
+ <p>Translates OID to Erlang digest type</p>
+ </desc>
+ </func>
+
<func>
<name name="pkix_match_dist_point" arity="2" since="OTP 19.0"/>
<fsummary>Checks whether the given distribution point matches the
diff --git a/lib/public_key/doc/src/public_key_records.xml b/lib/public_key/doc/src/public_key_records.xml
index 8075ecc4d1..3036d45c00 100644
--- a/lib/public_key/doc/src/public_key_records.xml
+++ b/lib/public_key/doc/src/public_key_records.xml
@@ -138,7 +138,22 @@
prime, % integer()
exponent, % integer()
coefficient % integer()
- }. </code>
+ }.
+
+#'RSASSA-PSS-params'{hashAlgorithm, % #'HashAlgorithm'{}},
+ maskGenAlgorithm, % #'MaskGenAlgorithm'{}},
+ saltLength, % integer(),
+ trailerField, % integer()
+ }.
+
+#'HashAlgorithm'{algorithm, % oid()
+ parameters % defaults to asn1_NOVALUE
+ }.
+
+#'MaskGenAlgorithm'{algorithm, % oid()
+ parameters, % defaults to asn1_NOVALUE
+ }.
+ </code>
</section>
diff --git a/lib/public_key/src/pubkey_cert.erl b/lib/public_key/src/pubkey_cert.erl
index a5836f6d07..1680845075 100644
--- a/lib/public_key/src/pubkey_cert.erl
+++ b/lib/public_key/src/pubkey_cert.erl
@@ -24,16 +24,29 @@
-include("public_key.hrl").
--export([init_validation_state/3, prepare_for_next_cert/2,
- validate_time/3, validate_signature/6,
- validate_issuer/4, validate_names/6,
+-export([init_validation_state/3,
+ prepare_for_next_cert/2,
+ validate_time/3,
+ validate_signature/6,
+ validate_issuer/4,
+ validate_names/6,
validate_extensions/4,
- normalize_general_name/1, is_self_signed/1,
- is_issuer/2, issuer_id/2, distribution_points/1,
- is_fixed_dh_cert/1, verify_data/1, verify_fun/4,
- select_extension/2, match_name/3,
- extensions_list/1, cert_auth_key_id/1, time_str_2_gregorian_sec/1,
- gen_test_certs/1, root_cert/2]).
+ normalize_general_name/1,
+ is_self_signed/1,
+ is_issuer/2,
+ issuer_id/2,
+ distribution_points/1,
+ is_fixed_dh_cert/1,
+ verify_data/1,
+ verify_fun/4,
+ select_extension/2,
+ match_name/3,
+ extensions_list/1,
+ cert_auth_key_id/1,
+ time_str_2_gregorian_sec/1,
+ gen_test_certs/1,
+ x509_pkix_sign_types/1,
+ root_cert/2]).
-define(NULL, 0).
@@ -503,6 +516,17 @@ gen_test_certs(
DERCAs = ca_config(RootCert, CAsKeys),
[{cert, DERCert}, {key, DERKey}, {cacerts, DERCAs}].
+
+x509_pkix_sign_types(#'SignatureAlgorithm'{algorithm = ?'id-RSASSA-PSS',
+ parameters = #'RSASSA-PSS-params'{hashAlgorithm = #'HashAlgorithm'{algorithm = Alg}}}) ->
+ Hash = public_key:pkix_hash_type(Alg),
+ {Hash, rsa_pss_pss, [{rsa_padding, rsa_pkcs1_pss_padding},
+ {rsa_pss_saltlen, -1},
+ {rsa_mgf1_md, Hash}]};
+x509_pkix_sign_types(#'SignatureAlgorithm'{algorithm = Alg}) ->
+ {Hash, Sign} = public_key:pkix_sign_types(Alg),
+ {Hash, Sign, []}.
+
%%%
-spec root_cert(string(), [cert_opt()]) -> test_root_cert().
%%
@@ -511,13 +535,16 @@ root_cert(Name, Opts) ->
PrivKey = gen_key(proplists:get_value(key, Opts, default_key_gen())),
TBS = cert_template(),
Issuer = subject("root", Name),
+ SignatureId = sign_algorithm(PrivKey, Opts),
+ SPI = public_key(PrivKey, SignatureId),
+
OTPTBS =
TBS#'OTPTBSCertificate'{
- signature = sign_algorithm(PrivKey, Opts),
+ signature = SignatureId,
issuer = Issuer,
validity = validity(Opts),
subject = Issuer,
- subjectPublicKeyInfo = public_key(PrivKey),
+ subjectPublicKeyInfo = SPI,
extensions = extensions(undefined, ca, Opts)
},
#{cert => public_key:pkix_sign(OTPTBS, PrivKey),
@@ -552,17 +579,21 @@ extensions_list(Extensions) ->
extract_verify_data(OtpCert, DerCert) ->
Signature = OtpCert#'OTPCertificate'.signature,
- SigAlgRec = OtpCert#'OTPCertificate'.signatureAlgorithm,
- SigAlg = SigAlgRec#'SignatureAlgorithm'.algorithm,
+ SigAlg = OtpCert#'OTPCertificate'.signatureAlgorithm,
PlainText = encoded_tbs_cert(DerCert),
- {DigestType,_} = public_key:pkix_sign_types(SigAlg),
+ {DigestType,_,_} = x509_pkix_sign_types(SigAlg),
{DigestType, PlainText, Signature}.
verify_signature(OtpCert, DerCert, Key, KeyParams) ->
{DigestType, PlainText, Signature} = extract_verify_data(OtpCert, DerCert),
case Key of
#'RSAPublicKey'{} ->
- public_key:verify(PlainText, DigestType, Signature, Key);
+ case KeyParams of
+ #'RSASSA-PSS-params'{} ->
+ public_key:verify(PlainText, DigestType, Signature, Key, verify_options(KeyParams));
+ 'NULL' ->
+ public_key:verify(PlainText, DigestType, Signature, Key)
+ end;
_ ->
public_key:verify(PlainText, DigestType, Signature, {Key, KeyParams})
end.
@@ -1119,6 +1150,8 @@ is_key(#'DSAPrivateKey'{}) ->
true;
is_key(#'RSAPrivateKey'{}) ->
true;
+is_key({#'RSAPrivateKey'{}, _}) ->
+ true;
is_key(#'ECPrivateKey'{}) ->
true;
is_key(_) ->
@@ -1174,10 +1207,18 @@ validity(Opts) ->
#'Validity'{notBefore={generalTime, Format(DefFrom)},
notAfter ={generalTime, Format(DefTo)}}.
-sign_algorithm(#'RSAPrivateKey'{}, Opts) ->
- Type = rsa_digest_oid(proplists:get_value(digest, Opts, sha1)),
- #'SignatureAlgorithm'{algorithm = Type,
- parameters = 'NULL'};
+sign_algorithm(#'RSAPrivateKey'{} = Key , Opts) ->
+ case proplists:get_value(rsa_padding, Opts, rsa_pkcs1_pss_padding) of
+ rsa_pkcs1_pss_padding ->
+ DigestId = rsa_digest_oid(proplists:get_value(digest, Opts, sha1)),
+ rsa_sign_algo(Key, DigestId, 'NULL');
+ rsa_pss_rsae ->
+ DigestId = rsa_digest_oid(proplists:get_value(digest, Opts, sha256)),
+ rsa_sign_algo(Key, DigestId, 'NULL')
+ end;
+sign_algorithm({#'RSAPrivateKey'{} = Key,#'RSASSA-PSS-params'{} = Params}, _Opts) ->
+ rsa_sign_algo(Key, ?'id-RSASSA-PSS', Params);
+
sign_algorithm(#'DSAPrivateKey'{p=P, q=Q, g=G}, _Opts) ->
#'SignatureAlgorithm'{algorithm = ?'id-dsa-with-sha1',
parameters = {params,#'Dss-Parms'{p=P, q=Q, g=G}}};
@@ -1185,6 +1226,16 @@ sign_algorithm(#'ECPrivateKey'{parameters = Parms}, Opts) ->
Type = ecdsa_digest_oid(proplists:get_value(digest, Opts, sha1)),
#'SignatureAlgorithm'{algorithm = Type,
parameters = Parms}.
+
+rsa_sign_algo(#'RSAPrivateKey'{}, ?'id-RSASSA-PSS' = Type, #'RSASSA-PSS-params'{} = Params) ->
+ #'SignatureAlgorithm'{algorithm = Type,
+ parameters = Params};
+rsa_sign_algo(#'RSAPrivateKey'{}, Type, Parms) ->
+ #'SignatureAlgorithm'{algorithm = Type,
+ parameters = Parms}.
+
+rsa_digest_oid(Oid) when is_tuple(Oid) ->
+ Oid;
rsa_digest_oid(sha1) ->
?'sha1WithRSAEncryption';
rsa_digest_oid(sha) ->
@@ -1196,8 +1247,10 @@ rsa_digest_oid(sha384) ->
rsa_digest_oid(sha256) ->
?'sha256WithRSAEncryption';
rsa_digest_oid(md5) ->
- ?'md5WithRSAEncryption'.
+ ?'md5WithRSAEncryption'.
+ecdsa_digest_oid(Oid) when is_tuple(Oid) ->
+ Oid;
ecdsa_digest_oid(sha1) ->
?'ecdsa-with-SHA1';
ecdsa_digest_oid(sha) ->
@@ -1229,12 +1282,13 @@ cert_chain(Role, IssuerCert, IssuerKey, [CAOpts | Rest], N, Acc) ->
cert(Role, #'OTPCertificate'{tbsCertificate = #'OTPTBSCertificate'{subject = Issuer}},
PrivKey, Key, Contact, Name, Opts, Type) ->
TBS = cert_template(),
+ SignAlgoId = sign_algorithm(PrivKey, Opts),
OTPTBS = TBS#'OTPTBSCertificate'{
- signature = sign_algorithm(PrivKey, Opts),
+ signature = SignAlgoId,
issuer = Issuer,
validity = validity(Opts),
subject = subject(Contact, atom_to_list(Role) ++ Name),
- subjectPublicKeyInfo = public_key(Key),
+ subjectPublicKeyInfo = public_key(Key, SignAlgoId),
extensions = extensions(Role, Type, Opts)
},
public_key:pkix_sign(OTPTBS, PrivKey).
@@ -1251,19 +1305,33 @@ default_key_gen() ->
{namedCurve, Oid}
end.
-public_key(#'RSAPrivateKey'{modulus=N, publicExponent=E}) ->
+public_key(#'RSAPrivateKey'{modulus=N, publicExponent=E},
+ #'SignatureAlgorithm'{algorithm = ?rsaEncryption,
+ parameters = #'RSASSA-PSS-params'{} = Params}) ->
+ Public = #'RSAPublicKey'{modulus=N, publicExponent=E},
+ Algo = #'PublicKeyAlgorithm'{algorithm= ?rsaEncryption, parameters = Params},
+ #'OTPSubjectPublicKeyInfo'{algorithm = Algo,
+ subjectPublicKey = Public};
+public_key({#'RSAPrivateKey'{modulus=N, publicExponent=E}, #'RSASSA-PSS-params'{} = Params},
+ #'SignatureAlgorithm'{algorithm = ?'id-RSASSA-PSS',
+ parameters = #'RSASSA-PSS-params'{} = Params}) ->
+ Public = #'RSAPublicKey'{modulus=N, publicExponent=E},
+ Algo = #'PublicKeyAlgorithm'{algorithm= ?'id-RSASSA-PSS', parameters= Params},
+ #'OTPSubjectPublicKeyInfo'{algorithm = Algo,
+ subjectPublicKey = Public};
+public_key(#'RSAPrivateKey'{modulus=N, publicExponent=E}, _) ->
Public = #'RSAPublicKey'{modulus=N, publicExponent=E},
Algo = #'PublicKeyAlgorithm'{algorithm= ?rsaEncryption, parameters='NULL'},
#'OTPSubjectPublicKeyInfo'{algorithm = Algo,
subjectPublicKey = Public};
-public_key(#'DSAPrivateKey'{p=P, q=Q, g=G, y=Y}) ->
+public_key(#'DSAPrivateKey'{p=P, q=Q, g=G, y=Y}, _) ->
Algo = #'PublicKeyAlgorithm'{algorithm= ?'id-dsa',
parameters={params, #'Dss-Parms'{p=P, q=Q, g=G}}},
#'OTPSubjectPublicKeyInfo'{algorithm = Algo, subjectPublicKey = Y};
public_key(#'ECPrivateKey'{version = _Version,
privateKey = _PrivKey,
parameters = Params,
- publicKey = PubKey}) ->
+ publicKey = PubKey}, _) ->
Algo = #'PublicKeyAlgorithm'{algorithm= ?'id-ecPublicKey', parameters=Params},
#'OTPSubjectPublicKeyInfo'{algorithm = Algo,
subjectPublicKey = #'ECPoint'{point = PubKey}}.
@@ -1306,6 +1374,9 @@ add_default_extensions(Defaults0, Exts) ->
end, Defaults0),
Exts ++ Defaults.
+encode_key({#'RSAPrivateKey'{}, #'RSASSA-PSS-params'{}} = Key) ->
+ {Asn1Type, DER, _} = public_key:pem_entry_encode('PrivateKeyInfo', Key),
+ {Asn1Type, DER};
encode_key(#'RSAPrivateKey'{} = Key) ->
{'RSAPrivateKey', public_key:der_encode('RSAPrivateKey', Key)};
encode_key(#'ECPrivateKey'{} = Key) ->
@@ -1313,3 +1384,11 @@ encode_key(#'ECPrivateKey'{} = Key) ->
encode_key(#'DSAPrivateKey'{} = Key) ->
{'DSAPrivateKey', public_key:der_encode('DSAPrivateKey', Key)}.
+verify_options(#'RSASSA-PSS-params'{saltLength = SaltLen,
+ maskGenAlgorithm =
+ #'MaskGenAlgorithm'{algorithm = ?'id-mgf1',
+ parameters = #'HashAlgorithm'{algorithm = HashOid}}}) ->
+ HashAlgo = public_key:pkix_hash_type(HashOid),
+ [{rsa_padding, rsa_pkcs1_pss_padding},
+ {rsa_pss_saltlen, SaltLen},
+ {rsa_mgf1_md, HashAlgo}].
diff --git a/lib/public_key/src/pubkey_cert_records.erl b/lib/public_key/src/pubkey_cert_records.erl
index cdb3723bf3..b556314ad1 100644
--- a/lib/public_key/src/pubkey_cert_records.erl
+++ b/lib/public_key/src/pubkey_cert_records.erl
@@ -110,7 +110,8 @@ supportedPublicKeyAlgorithms(?'rsaEncryption') -> 'RSAPublicKey';
supportedPublicKeyAlgorithms(?'id-dsa') -> 'DSAPublicKey';
supportedPublicKeyAlgorithms(?'dhpublicnumber') -> 'DHPublicKey';
supportedPublicKeyAlgorithms(?'id-keyExchangeAlgorithm') -> 'KEA-PublicKey';
-supportedPublicKeyAlgorithms(?'id-ecPublicKey') -> 'ECPoint'.
+supportedPublicKeyAlgorithms(?'id-ecPublicKey') -> 'ECPoint';
+supportedPublicKeyAlgorithms(?'id-RSASSA-PSS') -> 'RSAPublicKey'.
supportedCurvesTypes(?'characteristic-two-field') -> characteristic_two_field;
supportedCurvesTypes(?'prime-field') -> prime_field;
diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl
index a47b7148e7..e1f5f7576e 100644
--- a/lib/public_key/src/public_key.erl
+++ b/lib/public_key/src/public_key.erl
@@ -40,7 +40,8 @@
sign/3, sign/4, verify/4, verify/5,
generate_key/1,
compute_key/2, compute_key/3,
- pkix_sign/2, pkix_verify/2,
+ pkix_sign/2, pkix_verify/2,
+ pkix_hash_type/1,
pkix_sign_types/1,
pkix_is_self_signed/1,
pkix_is_fixed_dh_cert/1,
@@ -68,11 +69,12 @@
pki_asn1_type/0, asn1_type/0, ssh_file/0, der_encoded/0,
key_params/0, digest_type/0, issuer_name/0, oid/0]).
--type public_key() :: rsa_public_key() | dsa_public_key() | ec_public_key() | ed_public_key() .
--type private_key() :: rsa_private_key() | dsa_private_key() | ec_private_key() | ed_private_key() .
-
+-type public_key() :: rsa_public_key() | rsa_pss_public_key() | dsa_public_key() | ec_public_key() | ed_public_key() .
+-type private_key() :: rsa_private_key() | rsa_pss_private_key() | dsa_private_key() | ec_private_key() | ed_private_key() .
-type rsa_public_key() :: #'RSAPublicKey'{}.
--type rsa_private_key() :: #'RSAPrivateKey'{}.
+-type rsa_private_key() :: #'RSAPrivateKey'{}.
+-type rsa_pss_public_key() :: {#'RSAPublicKey'{}, #'RSASSA-PSS-params'{}}.
+-type rsa_pss_private_key() :: { #'RSAPrivateKey'{}, #'RSASSA-PSS-params'{}}.
-type dsa_private_key() :: #'DSAPrivateKey'{}.
-type dsa_public_key() :: {integer(), #'Dss-Parms'{}}.
-type ecpk_parameters() :: {ecParameters, #'ECParameters'{}} | {namedCurve, Oid::tuple()}.
@@ -293,6 +295,12 @@ der_priv_key_decode({'PrivateKeyInfo', v1,
{'PrivateKeyInfo_privateKeyAlgorithm', ?'rsaEncryption', _}, PrivKey, _}) ->
der_decode('RSAPrivateKey', PrivKey);
der_priv_key_decode({'PrivateKeyInfo', v1,
+ {'PrivateKeyInfo_privateKeyAlgorithm', ?'id-RSASSA-PSS',
+ {asn1_OPENTYPE, Parameters}}, PrivKey, _}) ->
+ Key = der_decode('RSAPrivateKey', PrivKey),
+ Params = der_decode('RSASSA-PSS-params', Parameters),
+ {Key, Params};
+der_priv_key_decode({'PrivateKeyInfo', v1,
{'PrivateKeyInfo_privateKeyAlgorithm', ?'id-dsa', {asn1_OPENTYPE, Parameters}}, PrivKey, _}) ->
{params, #'Dss-Parms'{p=P, q=Q, g=G}} = der_decode('DSAParams', Parameters),
X = der_decode('Prime-p', PrivKey),
@@ -307,34 +315,40 @@ der_priv_key_decode(PKCS8Key) ->
%%
%% Description: Encodes a public key entity with asn1 DER encoding.
%%--------------------------------------------------------------------
-
der_encode('PrivateKeyInfo', #'DSAPrivateKey'{p=P, q=Q, g=G, x=X}) ->
der_encode('PrivateKeyInfo',
- {'PrivateKeyInfo', v1,
- {'PrivateKeyInfo_privateKeyAlgorithm', ?'id-dsa',
- {asn1_OPENTYPE, der_encode('Dss-Parms', #'Dss-Parms'{p=P, q=Q, g=G})}},
+ {'PrivateKeyInfo', v1,
+ {'PrivateKeyInfo_privateKeyAlgorithm', ?'id-dsa',
+ {asn1_OPENTYPE, der_encode('Dss-Parms', #'Dss-Parms'{p=P, q=Q, g=G})}},
der_encode('Prime-p', X), asn1_NOVALUE});
der_encode('PrivateKeyInfo', #'RSAPrivateKey'{} = PrivKey) ->
der_encode('PrivateKeyInfo',
- {'PrivateKeyInfo', v1,
- {'PrivateKeyInfo_privateKeyAlgorithm', ?'rsaEncryption', {asn1_OPENTYPE, ?DER_NULL}},
- der_encode('RSAPrivateKey', PrivKey), asn1_NOVALUE});
+ {'PrivateKeyInfo', v1,
+ {'PrivateKeyInfo_privateKeyAlgorithm', ?'rsaEncryption',
+ {asn1_OPENTYPE, ?DER_NULL}},
+ der_encode('RSAPrivateKey', PrivKey), asn1_NOVALUE});
+der_encode('PrivateKeyInfo', {#'RSAPrivateKey'{} = PrivKey, Parameters}) ->
+ der_encode('PrivateKeyInfo',
+ {'PrivateKeyInfo', v1,
+ {'PrivateKeyInfo_privateKeyAlgorithm', ?'id-RSASSA-PSS',
+ {asn1_OPENTYPE, der_encode('RSASSA-PSS-params', Parameters)}},
+ der_encode('RSAPrivateKey', PrivKey), asn1_NOVALUE});
der_encode('PrivateKeyInfo', #'ECPrivateKey'{parameters = Parameters} = PrivKey) ->
der_encode('PrivateKeyInfo',
- {'PrivateKeyInfo', v1,
+ {'PrivateKeyInfo', v1,
{'PrivateKeyInfo_privateKeyAlgorithm', ?'id-ecPublicKey',
- {asn1_OPENTYPE, der_encode('EcpkParameters', Parameters)}},
- der_encode('ECPrivateKey', PrivKey#'ECPrivateKey'{parameters = asn1_NOVALUE}), asn1_NOVALUE});
+ {asn1_OPENTYPE, der_encode('EcpkParameters', Parameters)}},
+ der_encode('ECPrivateKey', PrivKey#'ECPrivateKey'{parameters = asn1_NOVALUE}),
+ asn1_NOVALUE});
der_encode(Asn1Type, Entity) when (Asn1Type == 'PrivateKeyInfo') or
(Asn1Type == 'EncryptedPrivateKeyInfo') ->
try
- {ok, Encoded} = 'PKCS-FRAME':encode(Asn1Type, Entity),
- Encoded
- catch
+ {ok, Encoded} = 'PKCS-FRAME':encode(Asn1Type, Entity),
+ Encoded
+ catch
error:{badmatch, {error, _}} = Error ->
- erlang:error(Error)
- end;
-
+ erlang:error(Error)
+ end;
der_encode(Asn1Type, Entity) when is_atom(Asn1Type) ->
try
{ok, Encoded} = 'OTP-PUB-KEY':encode(Asn1Type, Entity),
@@ -625,6 +639,22 @@ pkix_sign_types(?'ecdsa-with-SHA512') ->
{sha512, ecdsa}.
%%--------------------------------------------------------------------
+-spec pkix_hash_type(HashOid::oid()) -> DigestType:: md5 | crypto:sha1() | crypto:sha2().
+
+pkix_hash_type(?'id-sha1') ->
+ sha;
+pkix_hash_type(?'id-sha512') ->
+ sha512;
+pkix_hash_type(?'id-sha384') ->
+ sha384;
+pkix_hash_type(?'id-sha256') ->
+ sha256;
+pkix_hash_type('id-sha224') ->
+ sha224;
+pkix_hash_type('id-md5') ->
+ md5.
+
+%%--------------------------------------------------------------------
%% Description: Create digital signature.
%%--------------------------------------------------------------------
-spec sign(Msg, DigestType, Key) ->
@@ -768,12 +798,11 @@ pkix_match_dist_point(#'CertificateList'{
%% der encoded 'Certificate'{}
%%--------------------------------------------------------------------
pkix_sign(#'OTPTBSCertificate'{signature =
- #'SignatureAlgorithm'{algorithm = Alg}
+ #'SignatureAlgorithm'{}
= SigAlg} = TBSCert, Key) ->
-
Msg = pkix_encode('OTPTBSCertificate', TBSCert, otp),
- {DigestType, _} = pkix_sign_types(Alg),
- Signature = sign(Msg, DigestType, Key),
+ {DigestType, _, Opts} = pubkey_cert:x509_pkix_sign_types(SigAlg),
+ Signature = sign(Msg, DigestType, format_pkix_sign_key(Key), Opts),
Cert = #'OTPCertificate'{tbsCertificate= TBSCert,
signatureAlgorithm = SigAlg,
signature = Signature
@@ -796,6 +825,11 @@ pkix_verify(DerCert, #'RSAPublicKey'{} = RSAKey)
{DigestType, PlainText, Signature} = pubkey_cert:verify_data(DerCert),
verify(PlainText, DigestType, Signature, RSAKey);
+pkix_verify(DerCert, {#'RSAPublicKey'{} = RSAKey, #'RSASSA-PSS-params'{} = Params})
+ when is_binary(DerCert) ->
+ {DigestType, PlainText, Signature} = pubkey_cert:verify_data(DerCert),
+ verify(PlainText, DigestType, Signature, RSAKey, rsa_opts(Params));
+
pkix_verify(DerCert, Key = {#'ECPoint'{}, _})
when is_binary(DerCert) ->
{DigestType, PlainText, Signature} = pubkey_cert:verify_data(DerCert),
@@ -1257,7 +1291,11 @@ set_padding(Pad, Opts) ->
T =/= rsa_pad]
].
-
+format_pkix_sign_key({#'RSAPrivateKey'{} = Key, _}) ->
+ %% Params are handled in option arg
+ Key;
+format_pkix_sign_key(Key) ->
+ Key.
format_sign_key(Key = #'RSAPrivateKey'{}) ->
{rsa, format_rsa_private_key(Key)};
format_sign_key(#'DSAPrivateKey'{p = P, q = Q, g = G, x = X}) ->
@@ -1289,6 +1327,15 @@ format_verify_key(#'DSAPrivateKey'{y=Y, p=P, q=Q, g=G}) ->
format_verify_key(_) ->
badarg.
+rsa_opts(#'RSASSA-PSS-params'{maskGenAlgorithm =
+ #'MaskGenAlgorithm'{algorithm = ?'id-mgf1',
+ parameters = #'HashAlgorithm'{algorithm = HashAlgoOid}
+ }}) ->
+ HashAlgo = pkix_hash_type(HashAlgoOid),
+ [{rsa_padding, rsa_pkcs1_pss_padding},
+ {rsa_pss_saltlen, -1},
+ {rsa_mgf1_md, HashAlgo}].
+
do_pem_entry_encode(Asn1Type, Entity, CipherInfo, Password) ->
Der = der_encode(Asn1Type, Entity),
DecryptDer = pubkey_pem:cipher(Der, CipherInfo, Password),
@@ -1776,3 +1823,4 @@ format_details([]) ->
no_relevant_crls;
format_details(Details) ->
Details.
+
diff --git a/lib/public_key/test/public_key_SUITE.erl b/lib/public_key/test/public_key_SUITE.erl
index 1fd1d2fa76..3b2f1b7184 100644
--- a/lib/public_key/test/public_key_SUITE.erl
+++ b/lib/public_key/test/public_key_SUITE.erl
@@ -45,7 +45,9 @@ all() ->
pkix, pkix_countryname, pkix_emailaddress, pkix_path_validation,
pkix_iso_rsa_oid, pkix_iso_dsa_oid,
pkix_dsa_sha2_oid,
- pkix_crl, general_name,
+ pkix_crl,
+ pkix_hash_type,
+ general_name,
pkix_verify_hostname_cn,
pkix_verify_hostname_subjAltName,
pkix_verify_hostname_subjAltName_IP,
@@ -56,13 +58,13 @@ all() ->
].
groups() ->
- [{pem_decode_encode, [], [dsa_pem, rsa_pem, ec_pem, encrypted_pem,
+ [{pem_decode_encode, [], [dsa_pem, rsa_pem, rsa_pss_pss_pem, ec_pem, encrypted_pem,
dh_pem, cert_pem, pkcs7_pem, pkcs10_pem, ec_pem2,
rsa_priv_pkcs8, dsa_priv_pkcs8, ec_priv_pkcs8,
ec_pem_encode_generated,
gen_ec_param_prime_field, gen_ec_param_char_2_field
]},
- {sign_verify, [], [rsa_sign_verify, dsa_sign_verify]}
+ {sign_verify, [], [rsa_sign_verify, rsa_pss_sign_verify, dsa_sign_verify]}
].
%%-------------------------------------------------------------------
init_per_suite(Config) ->
@@ -101,6 +103,18 @@ init_per_testcase(gen_ec_param_prime_field=TC, Config) ->
init_per_testcase(gen_ec_param_char_2_field=TC, Config) ->
init_per_testcase_gen_ec_param(TC, sect571r1, Config);
+init_per_testcase(rsa_pss_sign_verify, Config) ->
+ Supports = crypto:supports(),
+ RSAOpts = proplists:get_value(rsa_opts, Supports),
+
+ case lists:member(rsa_pkcs1_pss_padding, RSAOpts)
+ andalso lists:member(rsa_pss_saltlen, RSAOpts)
+ andalso lists:member(rsa_mgf1_md, RSAOpts) of
+ true ->
+ Config;
+ false ->
+ {skip, not_supported_by_crypto}
+ end;
init_per_testcase(TestCase, Config) ->
case TestCase of
ec_pem_encode_generated ->
@@ -204,6 +218,19 @@ rsa_pem(Config) when is_list(Config) ->
RSARawPemNoEndNewLines = strip_superfluous_newlines(RSARawPem),
RSARawPemNoEndNewLines = strip_superfluous_newlines(public_key:pem_encode([PubEntry1])).
+rsa_pss_pss_pem() ->
+ [{doc, "RSA PKCS8 RSASSA-PSS private key decode/encode"}].
+rsa_pss_pss_pem(Config) when is_list(Config) ->
+ Datadir = proplists:get_value(data_dir, Config),
+ {ok, RsaPem} = file:read_file(filename:join(Datadir, "rsa_pss_pss_key.pem")),
+ [{'PrivateKeyInfo', DerRSAKey, not_encrypted} = Entry0 ] = public_key:pem_decode(RsaPem),
+ {RSAKey, Parms} = public_key:der_decode('PrivateKeyInfo', DerRSAKey),
+ {RSAKey, Parms} = public_key:pem_entry_decode(Entry0),
+ true = check_entry_type(RSAKey, 'RSAPrivateKey'),
+ PrivEntry0 = public_key:pem_entry_encode('PrivateKeyInfo', {RSAKey, Parms}),
+ RSAPemNoEndNewLines = strip_superfluous_newlines(RsaPem),
+ RSAPemNoEndNewLines = strip_superfluous_newlines(public_key:pem_encode([PrivEntry0])).
+
rsa_priv_pkcs8() ->
[{doc, "RSA PKCS8 private key decode/encode"}].
rsa_priv_pkcs8(Config) when is_list(Config) ->
@@ -410,6 +437,24 @@ rsa_sign_verify(Config) when is_list(Config) ->
true = public_key:verify(Msg, md5, RSASign1, PublicRSA).
%%--------------------------------------------------------------------
+rsa_pss_sign_verify() ->
+ [{doc, "Checks that we can sign and verify rsa pss signatures."}].
+rsa_pss_sign_verify(Config) when is_list(Config) ->
+ CertChainConf = #{server_chain =>
+ #{root => [{digest, sha256}, {hardcode_rsa_key(1), pss_params(sha256)}],
+ intermediates => [[]],
+ peer => [{digest, sha256}, {hardcode_rsa_key(2), pss_params(sha256)}]},
+ client_chain =>
+ #{root => [{digest, sha256}, {hardcode_rsa_key(3), pss_params(sha256)}],
+ intermediates => [[]],
+ peer => [{digest, sha256}, {hardcode_rsa_key(4), pss_params(sha256)}]}},
+ #{client_config := ClientConf} = public_key:pkix_test_data(CertChainConf),
+ Cert = proplists:get_value(cert, ClientConf),
+ {#'RSAPrivateKey'{modulus=Mod, publicExponent=Exp}, Parms} = {hardcode_rsa_key(4), pss_params(sha256)},
+
+ public_key:pkix_verify(Cert, {#'RSAPublicKey'{modulus=Mod, publicExponent=Exp}, Parms}).
+
+%%--------------------------------------------------------------------
dsa_sign_verify() ->
[{doc, "Checks that we can sign and verify dsa signatures."}].
@@ -848,6 +893,21 @@ general_name(Config) when is_list(Config) ->
[{rfc822Name, DummyRfc822Name}],
authorityCertSerialNumber =
1}).
+
+%%--------------------------------------------------------------------
+
+pkix_hash_type() ->
+ [{doc, "Test API function pkix_hash_type/1"}].
+
+pkix_hash_type(Config) when is_list(Config) ->
+ sha = public_key:pkix_hash_type(?'id-sha1'),
+ sha512 = public_key:pkix_hash_type(?'id-sha512'),
+ sha384 = public_key:pkix_hash_type(?'id-sha384'),
+ sha256 = public_key:pkix_hash_type(?'id-sha256'),
+ sha224 = public_key:pkix_hash_type('id-sha224'),
+ md5 = public_key:pkix_hash_type('id-md5').
+
+
%%--------------------------------------------------------------------
pkix_test_data_all_default() ->
@@ -901,7 +961,7 @@ pkix_test_data(Config) when is_list(Config) ->
public_key:pkix_test_data(#{server_chain =>
#{root => [],
intermediates => [],
- peer => [{key, hardcode_rsa_key()}]},
+ peer => [{key, hardcode_rsa_key(1)}]},
client_chain =>
#{root => [{validity, {{Year-2, Month, Day},
{Year-1, Month, Day}}}],
@@ -1089,9 +1149,7 @@ incorrect_countryname_pkix_cert() ->
incorrect_emailaddress_pkix_cert() ->
<<48,130,3,74,48,130,2,50,2,9,0,133,49,203,25,198,156,252,230,48,13,6,9,42,134, 72,134,247,13,1,1,5,5,0,48,103,49,11,48,9,6,3,85,4,6,19,2,65,85,49,19,48,17, 6,3,85,4,8,12,10,83,111,109,101,45,83,116,97,116,101,49,33,48,31,6,3,85,4,10, 12,24,73,110,116,101,114,110,101,116,32,87,105,100,103,105,116,115,32,80,116, 121,32,76,116,100,49,32,48,30,6,9,42,134,72,134,247,13,1,9,1,12,17,105,110, 118,97,108,105,100,64,101,109,97,105,108,46,99,111,109,48,30,23,13,49,51,49, 49,48,55,50,48,53,54,49,56,90,23,13,49,52,49,49,48,55,50,48,53,54,49,56,90, 48,103,49,11,48,9,6,3,85,4,6,19,2,65,85,49,19,48,17,6,3,85,4,8,12,10,83,111, 109,101,45,83,116,97,116,101,49,33,48,31,6,3,85,4,10,12,24,73,110,116,101, 114,110,101,116,32,87,105,100,103,105,116,115,32,80,116,121,32,76,116,100,49, 32,48,30,6,9,42,134,72,134,247,13,1,9,1,12,17,105,110,118,97,108,105,100,64, 101,109,97,105,108,46,99,111,109,48,130,1,34,48,13,6,9,42,134,72,134,247,13, 1,1,1,5,0,3,130,1,15,0,48,130,1,10,2,130,1,1,0,190,243,49,213,219,60,232,105, 1,127,126,9,130,15,60,190,78,100,148,235,246,223,21,91,238,200,251,84,55,212, 78,32,120,61,85,172,0,144,248,5,165,29,143,79,64,178,51,153,203,76,115,238, 192,49,173,37,121,203,89,62,157,13,181,166,30,112,154,40,202,140,104,211,157, 73,244,9,78,236,70,153,195,158,233,141,42,238,2,143,160,225,249,27,30,140, 151,176,43,211,87,114,164,108,69,47,39,195,123,185,179,219,28,218,122,53,83, 77,48,81,184,14,91,243,12,62,146,86,210,248,228,171,146,225,87,51,146,155, 116,112,238,212,36,111,58,41,67,27,6,61,61,3,84,150,126,214,121,57,38,12,87, 121,67,244,37,45,145,234,131,115,134,58,194,5,36,166,52,59,229,32,47,152,80, 237,190,58,182,248,98,7,165,198,211,5,31,231,152,116,31,108,71,218,64,188, 178,143,27,167,79,15,112,196,103,116,212,65,197,94,37,4,132,103,91,217,73, 223,207,185,7,153,221,240,232,31,44,102,108,82,83,56,242,210,214,74,71,246, 177,217,148,227,220,230,4,176,226,74,194,37,2,3,1,0,1,48,13,6,9,42,134,72, 134,247,13,1,1,5,5,0,3,130,1,1,0,89,247,141,154,173,123,123,203,143,85,28,79, 73,37,164,6,17,89,171,224,149,22,134,17,198,146,158,192,241,41,253,58,230, 133,71,189,43,66,123,88,15,242,119,227,249,99,137,61,200,54,161,0,177,167, 169,114,80,148,90,22,97,78,162,181,75,93,209,116,245,46,81,232,64,157,93,136, 52,57,229,113,197,218,113,93,42,161,213,104,205,137,30,144,183,58,10,98,47, 227,177,96,40,233,98,150,209,217,68,22,221,133,27,161,152,237,46,36,179,59, 172,97,134,194,205,101,137,71,192,57,153,20,114,27,173,233,166,45,56,0,61, 205,45,202,139,7,132,103,248,193,157,184,123,43,62,172,236,110,49,62,209,78, 249,83,219,133,1,213,143,73,174,16,113,143,189,41,84,60,128,222,30,177,104, 134,220,52,239,171,76,59,176,36,113,176,214,118,16,44,235,21,167,199,216,200, 76,219,142,248,13,70,145,205,216,230,226,148,97,223,216,179,68,209,222,63, 140,137,24,164,192,149,194,79,119,247,75,159,49,116,70,241,70,116,11,40,119, 176,157,36,160,102,140,255,34,248,25,231,136,59>>.
-
-
-hardcode_rsa_key() ->
+hardcode_rsa_key(1) ->
#'RSAPrivateKey'{
version = 'two-prime',
modulus = 23995666614853919027835084074500048897452890537492185072956789802729257783422306095699263934587064480357348855732149402060270996295002843755712064937715826848741191927820899197493902093529581182351132392364214171173881547273475904587683433713767834856230531387991145055273426806331200574039205571401702219159773947658558490957010003143162250693492642996408861265758000254664396313741422909188635443907373976005987612936763564996605457102336549804831742940035613780926178523017685712710473543251580072875247250504243621640157403744718833162626193206685233710319205099867303242759099560438381385658382486042995679707669,
@@ -1102,4 +1160,51 @@ hardcode_rsa_key() ->
exponent1 = 119556097830058336212015217380447172615655659108450823901745048534772786676204666783627059584226579481512852103690850928442711896738555003036938088452023283470698275450886490965004917644550167427154181661417665446247398284583687678213495921811770068712485038160606780733330990744565824684470897602653233516609,
exponent2 = 41669135975672507953822256864985956439473391144599032012999352737636422046504414744027363535700448809435637398729893409470532385959317485048904982111185902020526124121798693043976273393287623750816484427009887116945685005129205106462566511260580751570141347387612266663707016855981760014456663376585234613993,
coefficient = 76837684977089699359024365285678488693966186052769523357232308621548155587515525857011429902602352279058920284048929101483304120686557782043616693940283344235057989514310975192908256494992960578961614059245280827077951132083993754797053182279229469590276271658395444955906108899267024101096069475145863928441,
+ otherPrimeInfos = asn1_NOVALUE};
+
+hardcode_rsa_key(2) ->
+ #'RSAPrivateKey'{
+ version = 'two-prime',
+ modulus = 21343679768589700771839799834197557895311746244621307033143551583788179817796325695589283169969489517156931770973490560582341832744966317712674900833543896521418422508485833901274928542544381247956820115082240721897193055368570146764204557110415281995205343662628196075590438954399631753508888358737971039058298703003743872818150364935790613286541190842600031570570099801682794056444451081563070538409720109449780410837763602317050353477918147758267825417201591905091231778937606362076129350476690460157227101296599527319242747999737801698427160817755293383890373574621116766934110792127739174475029121017282777887777,
+ publicExponent = 17,
+ privateExponent = 18832658619343853622211588088997845201745658451136447382185486691577805721584993260814073385267196632785528033211903435807948675951440868570007265441362261636545666919252206383477878125774454042314841278013741813438699754736973658909592256273895837054592950290554290654932740253882028017801960316533503857992358685308186680144968293076156011747178275038098868263178095174694099811498968993700538293188879611375604635940554394589807673542938082281934965292051746326331046224291377703201248790910007232374006151098976879987912446997911775904329728563222485791845480864283470332826504617837402078265424772379987120023773,
+ prime1 = 146807662748886761089048448970170315054939768171908279335181627815919052012991509112344782731265837727551849787333310044397991034789843793140419387740928103541736452627413492093463231242466386868459637115999163097726153692593711599245170083315894262154838974616739452594203727376460632750934355508361223110419,
+ prime2 = 145385325050081892763917667176962991350872697916072592966410309213561884732628046256782356731057378829876640317801978404203665761131810712267778698468684631707642938779964806354584156202882543264893826268426566901882487709510744074274965029453915224310656287149777603803201831202222853023280023478269485417083,
+ exponent1 = 51814469205489445090252393754177758254684624060673510353593515699736136004585238510239335081623236845018299924941168250963996835808180162284853901555621683602965806809675350150634081614988136541809283687999704622726877773856604093851236499993845033701707873394143336209718962603456693912094478414715725803677,
+ exponent2 = 51312467664734785681382706062457526359131540440966797517556579722433606376221663384746714140373192528191755406283051201483646739222992016094510128871300458249756331334105225772206172777487956446433115153562317730076172132768497908567634716277852432109643395464627389577600646306666889302334125933506877206029,
+ coefficient = 30504662229874176232343608562807118278893368758027179776313787938167236952567905398252901545019583024374163153775359371298239336609182249464886717948407152570850677549297935773605431024166978281486607154204888016179709037883348099374995148481968169438302456074511782717758301581202874062062542434218011141540,
+ otherPrimeInfos = asn1_NOVALUE};
+hardcode_rsa_key(3) ->
+ #'RSAPrivateKey'{
+ version = 'two-prime',
+ modulus = 25089040456112869869472694987833070928503703615633809313972554887193090845137746668197820419383804666271752525807484521370419854590682661809972833718476098189250708650325307850184923546875260207894844301992963978994451844985784504212035958130279304082438876764367292331581532569155681984449177635856426023931875082020262146075451989132180409962870105455517050416234175675478291534563995772675388370042873175344937421148321291640477650173765084699931690748536036544188863178325887393475703801759010864779559318631816411493486934507417755306337476945299570726975433250753415110141783026008347194577506976486290259135429,
+ publicExponent = 17,
+ privateExponent = 8854955455098659953931539407470495621824836570223697404931489960185796768872145882893348383311931058684147950284994536954265831032005645344696294253579799360912014817761873358888796545955974191021709753644575521998041827642041589721895044045980930852625485916835514940558187965584358347452650930302268008446431977397918214293502821599497633970075862760001650736520566952260001423171553461362588848929781360590057040212831994258783694027013289053834376791974167294527043946669963760259975273650548116897900664646809242902841107022557239712438496384819445301703021164043324282687280801738470244471443835900160721870265,
+ prime1 = 171641816401041100605063917111691927706183918906535463031548413586331728772311589438043965564336865070070922328258143588739626712299625805650832695450270566547004154065267940032684307994238248203186986569945677705100224518137694769557564475390859269797990555863306972197736879644001860925483629009305104925823,
+ prime2 =146170909759497809922264016492088453282310383272504533061020897155289106805616042710009332510822455269704884883705830985184223718261139908416790475825625309815234508695722132706422885088219618698987115562577878897003573425367881351537506046253616435685549396767356003663417208105346307649599145759863108910523,
+ exponent1 = 60579464612132153154728441333538327425711971378777222246428851853999433684345266860486105493295364142377972586444050678378691780811632637288529186629507258781295583787741625893888579292084087601124818789392592131211843947578009918667375697196773859928702549128225990187436545756706539150170692591519448797349,
+ exponent2 = 137572620950115585809189662580789132500998007785886619351549079675566218169991569609420548245479957900898715184664311515467504676010484619686391036071176762179044243478326713135456833024206699951987873470661533079532774988581535389682358631768109586527575902839864474036157372334443583670210960715165278974609,
+ coefficient = 15068630434698373319269196003209754243798959461311186548759287649485250508074064775263867418602372588394608558985183294561315208336731894947137343239541687540387209051236354318837334154993136528453613256169847839789803932725339395739618592522865156272771578671216082079933457043120923342632744996962853951612,
+ otherPrimeInfos = asn1_NOVALUE};
+hardcode_rsa_key(4) ->
+ #'RSAPrivateKey'{
+ version ='two-prime',
+ modulus = 28617237755030755643854803617273584643843067580642149032833640135949799721163782522787597288521902619948688786051081993247908700824196122780349730169173433743054172191054872553484065655968335396052034378669869864779940355219732200954630251223541048434478476115391643898092650304645086338265930608997389611376417609043761464100338332976874588396803891301015812818307951159858145399281035705713082131199940309445719678087542976246147777388465712394062188801177717719764254900022006288880246925156931391594131839991579403409541227225173269459173129377291869028712271737734702830877034334838181789916127814298794576266389,
+ publicExponent = 17,
+ privateExponent = 26933870828264240605980991639786903194205240075898493207372837775011576208154148256741268036255908348187001210401018346586267012540419880263858569570986761169933338532757527109161473558558433313931326474042230460969355628442100895016122589386862163232450330461545076609969553227901257730132640573174013751883368376011370428995523268034111482031427024082719896108094847702954695363285832195666458915142143884210891427766607838346722974883433132513540317964796373298134261669479023445911856492129270184781873446960437310543998533283339488055776892320162032014809906169940882070478200435536171854883284366514852906334641,
+ prime1 = 177342190816702392178883147766999616783253285436834252111702533617098994535049411784501174309695427674025956656849179054202187436663487378682303508229883753383891163725167367039879190685255046547908384208614573353917213168937832054054779266431207529839577747601879940934691505396807977946728204814969824442867,
+ prime2 = 161367340863680900415977542864139121629424927689088951345472941851682581254789586032968359551717004797621579428672968948552429138154521719743297455351687337112710712475376510559020211584326773715482918387500187602625572442687231345855402020688502483137168684570635690059254866684191216155909970061793538842967,
+ exponent1 = 62591361464718491357252875682470452982324688977706206627659717747211409835899792394529826226951327414362102349476180842659595565881230839534930649963488383547255704844176717778780890830090016428673547367746320007264898765507470136725216211681602657590439205035957626212244060728285168687080542875871702744541,
+ exponent2 = 28476589564178982426348978152495139111074987239250991413906989738532220221433456358759122273832412611344984605059935696803369847909621479954699550944415412431654831613301737157474154985469430655673456186029444871051571607533040825739188591886206320553618003159523945304574388238386685203984112363845918619347,
+ coefficient = 34340318160575773065401929915821192439103777558577109939078671096408836197675640654693301707202885840826672396546056002756167635035389371579540325327619480512374920136684787633921441576901246290213545161954865184290700344352088099063404416346968182170720521708773285279884132629954461545103181082503707725012,
otherPrimeInfos = asn1_NOVALUE}.
+
+pss_params(sha256) ->
+ #'RSASSA-PSS-params'{
+ hashAlgorithm = #'HashAlgorithm'{algorithm = ?'id-sha256'},
+ maskGenAlgorithm = #'MaskGenAlgorithm'{algorithm = ?'id-mgf1',
+ parameters = #'HashAlgorithm'{algorithm = ?'id-sha256'}
+ },
+ saltLength = 32,
+ trailerField = 1}.
+
diff --git a/lib/public_key/test/public_key_SUITE_data/rsa_pss_pss_key.pem b/lib/public_key/test/public_key_SUITE_data/rsa_pss_pss_key.pem
new file mode 100644
index 0000000000..65032269c1
--- /dev/null
+++ b/lib/public_key/test/public_key_SUITE_data/rsa_pss_pss_key.pem
@@ -0,0 +1,29 @@
+-----BEGIN PRIVATE KEY-----
+MIIE7wIBADA9BgkqhkiG9w0BAQowMKANMAsGCWCGSAFlAwQCAaEaMBgGCSqGSIb3
+DQEBCDALBglghkgBZQMEAgGiAwIBIASCBKkwggSlAgEAAoIBAQDDlygksUEAajpd
+Vquo9XIAyTd9ZJ+55hNmhBfhn3lHz3ryPD+0XlgCE9qsKwfR7iYaqmnNilQnsxWp
+MGXAgOlC1+w5zh8qHvrI5wX+A6U9N8leIOSgFuFNP0FMMG7I677QzRxGFqKX1o4V
+73JWqnHCfnfHRyZY9xM0tYbJKNbRO7Hy4jKBPl3ptPHUoTltr4WYTOpgstcEamdi
+iif+0U4bQvVltNg9pzFEjkAktTUGn92W5CgLnsbPXxBo6a/kUlHcgmhYbpOXEjCP
+ufZLgsQo8iF2Bq8eWMEsByjr0chQjzrfZAUVtD8Hmh2uMVAPQFAHUkaLj2tHukL+
+s9tAaWKNAgMBAAECggEBAIzgfwWOtmb6HHfGSXY085wlUlZ696EKWsboNdtI5i4W
+/1Mimi/sFC/K5SJFDCjlA4UJYZOuItdFYkCun1t8foaqx3cLQ98u2SuDWwmOzqG9
+YMjvoDy+viDJgtrBt8n4I0R5t/ezrgD3hPe/s/dAZRfVx6g9Ux2ZOLgqV57kT3X7
+6paEz3jrIMvuoXQCsi9Qh+eJQ23/sAcc7OHQ7uD8QJVudEBnSHQ+ttvOPXhr7tba
+8NuNVa6E/KewkKHRAZqBTJolCVyPtWmvfaDwdJtunCvyR1w3Rv1adZLK4YRFz+vc
+sOMK+K1c2aojA+/Fnba19inNq13j6Dwqmq8Ho7MZwHECgYEA6aSx7/93S1VGpxQ9
+KqFE4Fy9ylliC/hanc9qOcfEIo0tDus9lfpuPp+aOXML0msVkIfhCnaru32qtnaI
+AQkIbPhSZFvC/i6BibpArXINbDzTS/46zZHehXskjWFGw+iRm/YI7MBuCmWzSnFO
+YUwSKRIPKZKyXswFzP8RsQO/QbsCgYEA1k5SamQheuKdo/X40ShWTTOoDlpL4Sir
+b2zTnEqlHyMv8c7w880hPf4P+0pqrKyf7jmEykJvp1qSAmyMUCWzrKTr8gQ2sMyb
+zj90cEm++M5YIQh5lPJy4pGqmCliJXqkt+zT1xmnRASwMNQOnU2bBmXkve/ofb4M
+dEwyig/nZFcCgYBLWPilTD6dhce+NBGxwMZkkKQIMKEk+RfIEs7QCXNgLSUdzZFT
+36pT+caTxl1Go5AVxyw04qZpVZKLO1iK9O3Jrp9rjAgrTrYpw23+QWzAvjDqLfeq
+ueMIKvlTus5GeacTo9mm+DvEkJ2sYTQEvrKQmilXn950IdmxDYUYD/xK5wKBgQDQ
+5ON9BUGFUSQsUHVLG7CT7EhiRS41ubjyEfhrHm+53Ei9weQpIcjHbsERR8aXrmTu
+h26i4QOI88XjSv+ymC19mfzLmcPdrnQpJL1RPvFCAZDyEhrBT1sg8rCBRcV/lv68
+scMEpuLecFt2HR5pwt3b7LJ9Wj8bYoctTaDt5va8XQKBgQDCr4hZB5haAcKmNm/g
+PjlaLdrDEIuuBjxMzX1t3PXwsEene1cE731v6fbmrDUa8AuJyMY80xhGrTTDQfS3
+QOu/6wtcUv/JC/06OwEaUlT/kdYek+zYfBm3b1sKP3HVKSxCLTcPcC4aQoAFqbEy
+3kuSVh03vVBdaP//qMPyeue17w==
+-----END PRIVATE KEY-----
diff --git a/lib/public_key/vsn.mk b/lib/public_key/vsn.mk
index 0008cf7a16..0a4bb38f70 100644
--- a/lib/public_key/vsn.mk
+++ b/lib/public_key/vsn.mk
@@ -1 +1 @@
-PUBLIC_KEY_VSN = 1.7.2
+PUBLIC_KEY_VSN = 1.8
diff --git a/lib/reltool/test/reltool_test_lib.erl b/lib/reltool/test/reltool_test_lib.erl
index 033d952d0a..88e5244b8b 100644
--- a/lib/reltool/test/reltool_test_lib.erl
+++ b/lib/reltool/test/reltool_test_lib.erl
@@ -238,7 +238,7 @@ wait_for_close() ->
end.
erl_libs() ->
- lists:sort([filename:absname(P) || P<-reltool_utils:erl_libs()]).
+ [filename:absname(P) || P<-reltool_utils:erl_libs()].
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% A small test server, which can be run standalone in a shell
diff --git a/lib/runtime_tools/doc/src/notes.xml b/lib/runtime_tools/doc/src/notes.xml
index f6cc85b4a0..c259f890ba 100644
--- a/lib/runtime_tools/doc/src/notes.xml
+++ b/lib/runtime_tools/doc/src/notes.xml
@@ -32,6 +32,27 @@
<p>This document describes the changes made to the Runtime_Tools
application.</p>
+<section><title>Runtime_Tools 1.15</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>Improved the presentation of allocations and carriers
+ in the <c>instrument</c> module.</p>
+ <p>
+ Own Id: OTP-16327</p>
+ </item>
+ <item>
+ <p>
+ Minor updates due to the new spawn improvements made.</p>
+ <p>
+ Own Id: OTP-16368 Aux Id: OTP-15251 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Runtime_Tools 1.14</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/runtime_tools/src/runtime_tools.app.src b/lib/runtime_tools/src/runtime_tools.app.src
index b55d50d040..d7c2975a5b 100644
--- a/lib/runtime_tools/src/runtime_tools.app.src
+++ b/lib/runtime_tools/src/runtime_tools.app.src
@@ -29,5 +29,5 @@
{applications, [kernel, stdlib]},
{env, []},
{mod, {runtime_tools, []}},
- {runtime_dependencies, ["stdlib-@OTP-15251@","mnesia-4.12","kernel-@OTP-15251@",
- "erts-@OTP-15251:OTP-16327@"]}]}.
+ {runtime_dependencies, ["stdlib-3.13","mnesia-4.12","kernel-7.0",
+ "erts-11.0"]}]}.
diff --git a/lib/runtime_tools/vsn.mk b/lib/runtime_tools/vsn.mk
index c01dd60009..4bacd1f571 100644
--- a/lib/runtime_tools/vsn.mk
+++ b/lib/runtime_tools/vsn.mk
@@ -1 +1 @@
-RUNTIME_TOOLS_VSN = 1.14
+RUNTIME_TOOLS_VSN = 1.15
diff --git a/lib/sasl/doc/specs/.gitignore b/lib/sasl/doc/specs/.gitignore
new file mode 100644
index 0000000000..322eebcb06
--- /dev/null
+++ b/lib/sasl/doc/specs/.gitignore
@@ -0,0 +1 @@
+specs_*.xml
diff --git a/lib/sasl/doc/src/Makefile b/lib/sasl/doc/src/Makefile
index 684fd2b5e4..249b278f6b 100644
--- a/lib/sasl/doc/src/Makefile
+++ b/lib/sasl/doc/src/Makefile
@@ -53,6 +53,8 @@ XML_FILES = \
$(XML_PART_FILES) $(XML_REF3_FILES) $(XML_REF4_FILES) \
$(XML_REF6_FILES) $(XML_APPLICATION_FILES)
+TOP_SPECS_FILE = specs.xml
+
# ----------------------------------------------------
include $(ERL_TOP)/make/doc.mk
diff --git a/lib/sasl/doc/src/notes.xml b/lib/sasl/doc/src/notes.xml
index 5982ce005d..71705080be 100644
--- a/lib/sasl/doc/src/notes.xml
+++ b/lib/sasl/doc/src/notes.xml
@@ -31,6 +31,45 @@
</header>
<p>This document describes the changes made to the SASL application.</p>
+<section><title>SASL 4.0</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Remove usage and documentation of old requests of the
+ I/O-protocol.</p>
+ <p>
+ Own Id: OTP-15695</p>
+ </item>
+ <item>
+ <p>
+ <c>systools:make_script/2</c> now accepts the name of the
+ boot file to create, it is not restricted to only
+ <c>RelName.boot</c> or <c>start.boot</c>.</p>
+ <p>
+ <c>systools:make_tar/2</c> now accepts the option
+ <c>extra_files</c> to add any extra non release related
+ files to the tar file.</p>
+ <p>
+ Own Id: OTP-16561 Aux Id: PR-2420 </p>
+ </item>
+ <item>
+ <p>
+ <seemfa
+ marker="systools#make_tar/1"><c>systools:make_tar/1,2</c></seemfa>
+ now filters out any tools from erts if included in the
+ release tar ball. See the documentation for more details.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16603</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>SASL 3.4.2</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/sasl/doc/src/specs.xml b/lib/sasl/doc/src/specs.xml
new file mode 100644
index 0000000000..f64b7df114
--- /dev/null
+++ b/lib/sasl/doc/src/specs.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<specs xmlns:xi="http://www.w3.org/2001/XInclude">
+ <xi:include href="../specs/specs_systools.xml"/>
+</specs>
diff --git a/lib/sasl/doc/src/systools.xml b/lib/sasl/doc/src/systools.xml
index 483445f954..13878d379a 100644
--- a/lib/sasl/doc/src/systools.xml
+++ b/lib/sasl/doc/src/systools.xml
@@ -251,8 +251,8 @@
warnings are issued for calls to undefined functions.</p>
<p>By default, errors and warnings are printed to tty and
the function returns <c>ok</c> or <c>error</c>. If option
- <c>silent</c> is specified, the function instead returns
<c>{ok,Module,Warnings}</c> or <c>{error,Module,Error}</c>.
+ <c>silent</c> is specified, the function instead returns
Warnings and errors can be converted to strings by calling
<c>Module:format_warning(Warnings)</c> or
<c>Module:format_error(Error)</c>.</p>
@@ -265,25 +265,9 @@
</func>
<func>
- <name since="">make_tar(Name) -> Result</name>
- <name since="">make_tar(Name, [Opt]) -> Result</name>
+ <name name="make_tar" arity="1" since=""/>
+ <name name="make_tar" arity="2" since=""/>
<fsummary>Creates a release package.</fsummary>
- <type>
- <v>Name = string()</v>
- <v>Opt = {dirs,[IncDir]} | {path,[Dir]} | {variables,[Var]} | {var_tar,VarTar} | {erts,Dir} | src_tests | exref | {exref,[App]} | silent | {outdir,Dir} | | no_warn_sasl | warnings_as_errors | {extra_files, ExtraFiles}</v>
- <v>&nbsp;Dir = string()</v>
- <v>&nbsp;IncDir = src | include | atom()</v>
- <v>&nbsp;Var = {VarName,PreFix}</v>
- <v>&nbsp;&nbsp;VarName = Prefix = string()</v>
- <v>&nbsp;VarTar = include | ownfile | omit</v>
- <v>&nbsp;Machine = atom()</v>
- <v>&nbsp;App = atom()</v>
- <v>Result = ok | error | {ok,Module,Warnings} | {error,Module,Error}</v>
- <v>&nbsp;Module = atom()</v>
- <v>&nbsp;Warning = Error = term()</v>
- <v>&nbsp;ExtraFiles = [{NameInArchive, file:filename_all()}]</v>
- <v>&nbsp;NameInArchive = string()</v>
- </type>
<desc>
<p>Creates a release package file <c>Name.tar.gz</c>.
This file must be uncompressed and unpacked on the target
@@ -364,8 +348,10 @@ myapp-1/ebin/myapp.app
specified using option <c>path</c>. In the case of <c>sys.config</c>
it is not included if <c>sys.config.src</c> is found.</p>
<p>If the release package is to contain a new Erlang runtime
- system, the <c>bin</c> directory of the specified runtime
- system <c>{erts,Dir}</c> is copied to <c>erts-ErtsVsn/bin</c>.</p>
+ system, the <c>erts-ErtsVsn/bin</c> directory of the specified runtime
+ system <c>{erts,Dir}</c> is copied to <c>erts-ErtsVsn/bin</c>. Some
+ erts executables are not copied by default, if you want to include all
+ executables you can give the <c>erts_all</c> option.</p>
<p>All checks with function
<seemfa marker="#make_script/1"><c>make_script</c></seemfa>
are performed before the release package is created.
diff --git a/lib/sasl/src/sasl.appup.src b/lib/sasl/src/sasl.appup.src
index b795123645..621454d6a4 100644
--- a/lib/sasl/src/sasl.appup.src
+++ b/lib/sasl/src/sasl.appup.src
@@ -21,6 +21,7 @@
%% versions from the following OTP releases:
%% - OTP 21
%% - OTP 22
+%% - OTP 23
%%
%% We also allow upgrade from, and downgrade to all
%% versions that have branched off from the above
@@ -34,7 +35,8 @@
{<<"^3\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^3\\.4$">>,[restart_new_emulator]},
{<<"^3\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^3\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}],
+ {<<"^3\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]}],
[{<<"^3\\.2$">>,[restart_new_emulator]},
{<<"^3\\.2\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^3\\.2\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
@@ -42,4 +44,5 @@
{<<"^3\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^3\\.4$">>,[restart_new_emulator]},
{<<"^3\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^3\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}]}.
+ {<<"^3\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]}]}.
diff --git a/lib/sasl/src/systools.erl b/lib/sasl/src/systools.erl
index 34eca6679f..a6aa4919ec 100644
--- a/lib/sasl/src/systools.erl
+++ b/lib/sasl/src/systools.erl
@@ -62,8 +62,32 @@ make_script(RelName, Opt) ->
%% release package and erts specifies that the erts-Vsn/bin directory
%% should be included in the release package and there it can be found.
%%-----------------------------------------------------------------
+-spec make_tar(Name) -> Result when
+ Name :: string(),
+ Result :: ok | error | {ok, Module :: module(), Warnings :: term()} |
+ {error, Module :: module(), Error :: term()}.
make_tar(RelName) -> make_tar(RelName, []).
+-spec make_tar(Name, Opts) -> Result when
+ Name :: string(),
+ Opts :: [Opt],
+ Opt :: {dirs,[IncDir]} | {path,[Dir]} |
+ {variables,[Var]} | {var_tar,VarTar} |
+ {erts,Dir} | erts_all | src_tests | exref |
+ {exref,[App]} | silent | {outdir,Dir} |
+ no_warn_sasl | warnings_as_errors |
+ {extra_files, ExtraFiles},
+ Dir :: file:filename_all(),
+ IncDir :: src | include | atom(),
+ Var :: {VarName,PreFix},
+ VarName :: string(),
+ PreFix :: string(),
+ VarTar :: include | ownfile | omit,
+ App :: atom(),
+ Result :: ok | error | {ok, Module :: module(), Warnings :: term()} |
+ {error, Module :: module(), Error :: term()},
+ ExtraFiles :: [{NameInArchive, file:filename_all()}],
+ NameInArchive :: string().
make_tar(RelName, Opt) ->
systools_make:make_tar(RelName, Opt).
diff --git a/lib/sasl/src/systools_make.erl b/lib/sasl/src/systools_make.erl
index 1565366d92..a371239823 100644
--- a/lib/sasl/src/systools_make.erl
+++ b/lib/sasl/src/systools_make.erl
@@ -27,8 +27,7 @@
-export([format_error/1, format_warning/1]).
--export([read_release/2, get_release/2, get_release/3,
- get_release/4, pack_app/1]).
+-export([read_release/2, get_release/2, get_release/3, pack_app/1]).
-export([read_application/4]).
@@ -47,11 +46,9 @@
-compile({inline,[{badarg,2}]}).
-ifdef(USE_ESOCK).
--define(ESOCK_SOCKET_MODS, [socket, socket_registry]).
--define(ESOCK_NET_MODS, [prim_net]).
+-define(ESOCK_MODS, [prim_net,prim_socket,socket_registry]).
-else.
--define(ESOCK_SOCKET_MODS, []).
--define(ESOCK_NET_MODS, []).
+-define(ESOCK_MODS, []).
-endif.
@@ -67,7 +64,6 @@
%% New options: {path,Path} can contain wildcards
%% src_tests
%% {variables,[{Name,AbsString}]}
-%% {machine, jam | beam | vee}
%% exref | {exref, [AppName]}
%% no_warn_sasl
%%-----------------------------------------------------------------
@@ -100,7 +96,7 @@ make_script(RelName, Output, Flags) when is_list(RelName),
Path1 = mk_path(Path0), % expand wildcards etc.
Path = make_set(Path1 ++ code:get_path()),
ModTestP = {member(src_tests, Flags),xref_p(Flags)},
- case get_release(RelName, Path, ModTestP, machine(Flags)) of
+ case get_release(RelName, Path, ModTestP) of
{ok, Release, Appls, Warnings0} ->
Warnings = wsasl(Flags, Warnings0),
case systools_lib:werror(Flags, Warnings) of
@@ -139,12 +135,6 @@ wsasl(Options, Warnings) ->
badarg(BadArg, Args) ->
erlang:error({badarg,BadArg}, Args).
-machine(Flags) ->
- case get_flag(machine,Flags) of
- {machine, Machine} when is_atom(Machine) -> Machine;
- _ -> false
- end.
-
get_script_name(RelName, Flags) ->
case get_flag(script_name,Flags) of
{script_name,ScriptName} when is_list(ScriptName) -> ScriptName;
@@ -364,7 +354,6 @@ add_apply_upgrade(Script,Args) ->
%% src_tests
%% exref | {exref, [AppName]}
%% {variables,[{Name,AbsString}]}
-%% {machine, jam | beam | vee}
%% {var_tar, include | ownfile | omit}
%% no_warn_sasl
%% warnings_as_errors
@@ -400,7 +389,7 @@ make_tar(RelName, Flags) when is_list(RelName), is_list(Flags) ->
Path1 = mk_path(Path0),
Path = make_set(Path1 ++ code:get_path()),
ModTestP = {member(src_tests, Flags),xref_p(Flags)},
- case get_release(RelName, Path, ModTestP, machine(Flags)) of
+ case get_release(RelName, Path, ModTestP) of
{ok, Release, Appls, Warnings0} ->
Warnings = wsasl(Flags, Warnings0),
case systools_lib:werror(Flags, Warnings) of
@@ -432,17 +421,13 @@ make_tar(RelName, Flags) ->
%%______________________________________________________________________
%% get_release(File, Path) ->
%% get_release(File, Path, ModTestP) ->
-%% get_release(File, Path, ModTestP, Machine) ->
%% {ok, #release, [{{Name,Vsn},#application}], Warnings} | {error, What}
get_release(File, Path) ->
- get_release(File, Path, {false,false}, false).
+ get_release(File, Path, {false,false}).
get_release(File, Path, ModTestP) ->
- get_release(File, Path, ModTestP, false).
-
-get_release(File, Path, ModTestP, Machine) ->
- case catch get_release1(File, Path, ModTestP, Machine) of
+ case catch get_release1(File, Path, ModTestP) of
{error, Error} ->
{error, ?MODULE, Error};
{'EXIT', Why} ->
@@ -451,12 +436,12 @@ get_release(File, Path, ModTestP, Machine) ->
Answer
end.
-get_release1(File, Path, ModTestP, Machine) ->
+get_release1(File, Path, ModTestP) ->
{ok, Release, Warnings1} = read_release(File, Path),
{ok, Appls0} = collect_applications(Release, Path),
{ok, Appls1} = check_applications(Appls0),
{ok, Appls2} = sort_used_and_incl_appls(Appls1, Release), % OTP-4121, OTP-9984
- {ok, Warnings2} = check_modules(Appls2, Path, ModTestP, Machine),
+ {ok, Warnings2} = check_modules(Appls2, Path, ModTestP),
{ok, Appls} = sort_appls(Appls2),
{ok, Release, Appls, Warnings1 ++ Warnings2}.
@@ -976,13 +961,13 @@ find_pos(N, Name, [_OtherAppl|OrderedAppls]) ->
find_pos(N+1, Name, OrderedAppls).
%%______________________________________________________________________
-%% check_modules(Appls, Path, TestP, Machine) ->
+%% check_modules(Appls, Path, TestP) ->
%% {ok, Warnings} | throw({error, What})
%% where Appls = [{App,Vsn}, #application}]
%% performs logical checking that we can find all the modules
%% etc.
-check_modules(Appls, Path, TestP, Machine) ->
+check_modules(Appls, Path, TestP) ->
%% first check that all the module names are unique
%% Make a list M1 = [{Mod,App,Dir}]
M1 = [{Mod,App,A#application.dir} ||
@@ -990,7 +975,7 @@ check_modules(Appls, Path, TestP, Machine) ->
Mod <- A#application.modules],
case duplicates(M1) of
[] ->
- case check_mods(M1, Appls, Path, TestP, Machine) of
+ case check_mods(M1, Appls, Path, TestP) of
{error, Errors} ->
throw({error, {modules, Errors}});
Return ->
@@ -1006,8 +991,8 @@ check_modules(Appls, Path, TestP, Machine) ->
%% Use the module extension of the running machine as extension for
%% the checked modules.
-check_mods(Modules, Appls, Path, {SrcTestP, XrefP}, Machine) ->
- SrcTestRes = check_src(Modules, Appls, Path, SrcTestP, Machine),
+check_mods(Modules, Appls, Path, {SrcTestP, XrefP}) ->
+ SrcTestRes = check_src(Modules, Appls, Path, SrcTestP),
XrefRes = check_xref(Appls, Path, XrefP),
Res = SrcTestRes ++ XrefRes,
case filter(fun({error, _}) -> true;
@@ -1023,8 +1008,8 @@ check_mods(Modules, Appls, Path, {SrcTestP, XrefP}, Machine) ->
{error, Errors}
end.
-check_src(Modules, Appls, Path, true, Machine) ->
- Ext = objfile_extension(Machine),
+check_src(Modules, Appls, Path, true) ->
+ Ext = code:objfile_extension(),
IncPath = create_include_path(Appls, Path),
append(map(fun(ModT) ->
{Mod,App,Dir} = ModT,
@@ -1038,7 +1023,7 @@ check_src(Modules, Appls, Path, true, Machine) ->
end
end,
Modules));
-check_src(_, _, _, _, _) ->
+check_src(_, _, _, _) ->
[].
check_xref(_Appls, _Path, false) ->
@@ -1136,11 +1121,6 @@ exists_xref(Flag) ->
_ -> Flag
end.
-objfile_extension(false) ->
- code:objfile_extension();
-objfile_extension(Machine) ->
- "." ++ atom_to_list(Machine).
-
check_mod(Mod,App,Dir,Ext,IncPath) ->
ObjFile = mod_to_filename(Dir, Mod, Ext),
case file:read_file_info(ObjFile) of
@@ -1578,12 +1558,23 @@ mandatory_modules() ->
%% This is the modules that are preloaded into the Erlang system.
preloaded() ->
- %% Sorted
- [atomics,counters,erl_init,erl_prim_loader,erl_tracer,erlang,
- erts_code_purger,erts_dirty_process_signal_handler,
- erts_internal,erts_literal_area_collector,
- init,persistent_term,prim_buffer,prim_eval,prim_file,
- prim_inet] ++ ?ESOCK_NET_MODS ++ [prim_zip] ++ ?ESOCK_SOCKET_MODS ++ [zlib].
+ lists:sort(
+ ?ESOCK_MODS ++
+ [atomics,counters,erl_init,erl_prim_loader,erl_tracer,erlang,
+ erts_code_purger,erts_dirty_process_signal_handler,
+ erts_internal,erts_literal_area_collector,
+ init,persistent_term,prim_buffer,prim_eval,prim_file,
+ prim_inet,prim_zip,zlib]).
+
+%%______________________________________________________________________
+%% This is the erts binaries that should *not* be part of a systool:make_tar package
+
+erts_binary_filter() ->
+ Cmds = ["typer", "dialyzer", "ct_run", "yielding_c_fun", "erlc"],
+ case os:type() of
+ {unix,_} -> Cmds;
+ {win32,_} -> [ [Cmd, ".exe"] || Cmd <- Cmds]
+ end.
%%______________________________________________________________________
%% Kernel processes; processes that are specially treated by the init
@@ -1895,7 +1886,7 @@ add_appl(Name, Vsn, App, Tar, Variables, Flags, Var) ->
Tar,
AppDir,
BinDir,
- objfile_extension(machine(Flags)))
+ code:objfile_extension())
end.
%%______________________________________________________________________
@@ -1974,18 +1965,26 @@ add_priv(ADir, ToDir, Tar) ->
end.
add_erts_bin(Tar, Release, Flags) ->
- case get_flag(erts,Flags) of
- {erts,ErtsDir} ->
- EVsn = Release#release.erts_vsn,
- FromDir = filename:join([to_list(ErtsDir),
- "erts-" ++ EVsn, "bin"]),
- dirp(FromDir),
- ToDir = filename:join("erts-" ++ EVsn, "bin"),
- add_to_tar(Tar, FromDir, ToDir);
+ case {get_flag(erts,Flags),member(erts_all,Flags)} of
+ {{erts,ErtsDir},true} ->
+ add_erts_bin(Tar, Release, ErtsDir, []);
+ {{erts,ErtsDir},false} ->
+ add_erts_bin(Tar, Release, ErtsDir, erts_binary_filter());
_ ->
ok
end.
+add_erts_bin(Tar, Release, ErtsDir, Filters) ->
+ FlattenedFilters = [filename:flatten(Filter) || Filter <- Filters],
+ EVsn = Release#release.erts_vsn,
+ FromDir = filename:join([to_list(ErtsDir),
+ "erts-" ++ EVsn, "bin"]),
+ ToDir = filename:join("erts-" ++ EVsn, "bin"),
+ {ok, Bins} = file:list_dir(FromDir),
+ [add_to_tar(Tar, filename:join(FromDir,Bin), filename:join(ToDir,Bin))
+ || Bin <- Bins, not lists:member(Bin, FlattenedFilters)],
+ ok.
+
%%______________________________________________________________________
%% Tar functions.
@@ -2179,9 +2178,6 @@ cas([{variables, V} | Args], X) when is_list(V) ->
error ->
cas(Args, X++[{variables, V}])
end;
-%%% machine ------------------------------------------------------------
-cas([{machine, M} | Args], X) when is_atom(M) ->
- cas(Args, X);
%%% exref --------------------------------------------------------------
cas([exref | Args], X) ->
cas(Args, X);
@@ -2248,6 +2244,8 @@ cat([{dirs, D} | Args], X) ->
%%% erts ---------------------------------------------------------------
cat([{erts, E} | Args], X) when is_list(E)->
cat(Args, X);
+cat([erts_all | Args], X) ->
+ cat(Args, X);
%%% src_tests ----------------------------------------------------
cat([src_tests | Args], X) ->
cat(Args, X);
@@ -2264,9 +2262,6 @@ cat([{var_tar, VT} | Args], X) when VT == include;
VT == ownfile;
VT == omit ->
cat(Args, X);
-%%% machine ------------------------------------------------------------
-cat([{machine, M} | Args], X) when is_atom(M) ->
- cat(Args, X);
%%% exref --------------------------------------------------------------
cat([exref | Args], X) ->
cat(Args, X);
diff --git a/lib/sasl/test/systools_SUITE.erl b/lib/sasl/test/systools_SUITE.erl
index fb1dc03dcd..3c60c0fa21 100644
--- a/lib/sasl/test/systools_SUITE.erl
+++ b/lib/sasl/test/systools_SUITE.erl
@@ -69,7 +69,7 @@ groups() ->
[tar_options, relname_tar, normal_tar, no_mod_vsn_tar, system_files_tar,
system_src_file_tar, invalid_system_files_tar, variable_tar,
src_tests_tar, var_tar, exref_tar, link_tar, no_sasl_tar,
- otp_9507_path_ebin, additional_files_tar]},
+ otp_9507_path_ebin, additional_files_tar, erts_tar]},
{relup, [],
[normal_relup, restart_relup, abnormal_relup, no_sasl_relup,
no_appup_relup, bad_appup_relup, app_start_type_relup, regexp_relup
@@ -1041,13 +1041,90 @@ additional_files_tar(Config) ->
ok.
-
system_files_tar(cleanup,Config) ->
Dir = ?privdir,
file:delete(filename:join(Dir,"sys.config")),
file:delete(filename:join(Dir,"relup")),
ok.
+erts_tar(Config) ->
+
+ {ok, OldDir} = file:get_cwd(),
+
+ {LatestDir, LatestName} = create_script(current_all,Config),
+
+ ERTS_VSN = erlang:system_info(version),
+ ERTS_DIR = fname(["erts-" ++ ERTS_VSN,bin]),
+
+ %% List of all expected executable files in erts/bin
+ %% This list needs to be kept up to date whenever a file is
+ %% added or removed.
+ {Default, Ignored} =
+ case os:type() of
+ {unix,_} ->
+ {["beam.smp","dyn_erl","epmd","erl","erl_call","erl_child_setup",
+ "erlexec","erl.src","escript","heart","inet_gethost","run_erl",
+ "start","start_erl.src","start.src","to_erl"],
+ ["ct_run","dialyzer","erlc","typer","yielding_c_fun"]};
+ {win32, _} ->
+ {["beam.smp.pdb","erl.exe",
+ "erl.pdb","erl_log.exe","erlexec.dll","erlsrv.exe","heart.exe",
+ "start_erl.exe","werl.exe","beam.smp.dll",
+ "epmd.exe","erl.ini","erl_call.exe",
+ "erlexec.pdb","escript.exe","inet_gethost.exe","werl.pdb"],
+ ["dialyzer.exe","erlc.exe","yielding_c_fun.exe","ct_run.exe","typer.exe"]}
+ end,
+
+ ErtsTarContent =
+ fun(TarName) ->
+ lists:sort(
+ [filename:basename(File)
+ || File <- tar_contents(TarName),
+ string:equal(filename:dirname(File),ERTS_DIR),
+ %% Filter out beam.*.smp.*
+ re:run(filename:basename(File), "beam\\.[^\\.]+\\.smp(\\.dll)?") == nomatch,
+ %% Filter out any erl_child_setup.*
+ re:run(filename:basename(File), "erl_child_setup\\..*") == nomatch
+ ])
+ end,
+
+ DataDir = filename:absname(?copydir),
+ LibDir = fname([DataDir, d_normal, lib]),
+ P = [fname([LibDir, 'db-2.1', ebin])],
+
+ ok = file:set_cwd(LatestDir),
+
+ {ok, _, []} = systools:make_script(LatestName, [silent, {path, P}, {script_name, "start"}]),
+ ok = systools:make_tar(LatestName, [{path, P}, {erts, code:root_dir()}]),
+ ErtsContent = ErtsTarContent(LatestName),
+
+ case lists:sort(Default) of
+ ErtsContent ->
+ ok;
+ Expected ->
+ ct:pal("Content: ~p",[ErtsContent]),
+ ct:pal("Expected: ~p",[Expected]),
+ ct:fail("Incorrect erts bin content")
+ end,
+
+ ok = systools:make_tar(LatestName, [{path, P},
+ {erts, code:root_dir()},
+ erts_all]),
+ ErtsAllContent = ErtsTarContent(LatestName),
+
+ case lists:sort(Default ++ Ignored) of
+ ErtsAllContent ->
+ ok;
+ ExpectedIgn ->
+ ct:pal("Content: ~p",[ErtsAllContent]),
+ ct:pal("Expected: ~p",[ExpectedIgn]),
+ ct:fail("Incorrect erts bin content")
+ end,
+
+ ok = file:set_cwd(OldDir),
+ ok.
+
+
%% make_tar: Check that sys.config.src and not sys.config is included
system_src_file_tar(Config) ->
{ok, OldDir} = file:get_cwd(),
@@ -2342,7 +2419,7 @@ delete_tree(Dir) ->
end.
tar_contents(Name) ->
- {ok, Cont} = erl_tar:table(Name ++ ".tar.gz", [compressed]),
+ {ok, Cont} = erl_tar:table(tar_name(Name), [compressed]),
Cont.
tar_name(Name) ->
diff --git a/lib/sasl/vsn.mk b/lib/sasl/vsn.mk
index fd045e49d5..9cab3fd792 100644
--- a/lib/sasl/vsn.mk
+++ b/lib/sasl/vsn.mk
@@ -1 +1 @@
-SASL_VSN = 3.4.2
+SASL_VSN = 4.0
diff --git a/lib/snmp/doc/src/notes.xml b/lib/snmp/doc/src/notes.xml
index d4d5dd2f35..52b0b26c84 100644
--- a/lib/snmp/doc/src/notes.xml
+++ b/lib/snmp/doc/src/notes.xml
@@ -34,7 +34,76 @@
</header>
- <section><title>SNMP 5.5</title>
+ <section><title>SNMP 5.6</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ For manager, fix PrivParams for SNMPv3 USM with AES
+ privacy; * In `snmp_usm:do_decrypt/3`, pass full
+ UsmSecParams to `snmp_usm:try_decrypt/5` as expected by
+ AES clause. * Change `snmpm_usm:aes_encrypt/3` to use
+ EngineBoots and EngineTime as cached by
+ `snmpm_config:get_usm_eboots/1` and
+ `snmpm_config:get_usm_etime/1` instead of
+ `snmpm_config:get_engine_boots/0` and
+ `snmpm_config:get_engine_time/0`. This ensures correct
+ msgPrivacyParameters are sent when AES is used. * Add
+ test `snmp.snmp_manager_SUITE.usm_priv_aes/1` to avoid
+ regression.</p>
+ <p>
+ Own Id: OTP-16541 Aux Id: #2544 </p>
+ </item>
+ <item>
+ <p>
+ Invalid character in (manager) usm config entry generator
+ function.</p>
+ <p>
+ Own Id: OTP-16552 Aux Id: ERL-1196 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Remove usage and documentation of old requests of the
+ I/O-protocol.</p>
+ <p>
+ Own Id: OTP-15695</p>
+ </item>
+ <item>
+ <p>
+ Calls of deprecated functions in the <seeguide
+ marker="crypto:new_api#the-old-api">Old Crypto
+ API</seeguide> are replaced by calls of their <seeguide
+ marker="crypto:new_api#the-new-api">substitutions</seeguide>.</p>
+ <p>
+ Own Id: OTP-16346</p>
+ </item>
+ <item>
+ <p>
+ Finalize deprecation. Already deprecated functions has a
+ "remove version 24" set and "new" functions added to list
+ of deprecated functions.</p>
+ <p>
+ Own Id: OTP-16463</p>
+ </item>
+ <item>
+ <p>Refactored the internal handling of deprecated and
+ removed functions.</p>
+ <p>
+ Own Id: OTP-16469</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SNMP 5.5</title>
<section><title>Improvements and New Features</title>
<list>
diff --git a/lib/snmp/test/snmp_agent_SUITE.erl b/lib/snmp/test/snmp_agent_SUITE.erl
index 62129c2543..c676bc487e 100644
--- a/lib/snmp/test/snmp_agent_SUITE.erl
+++ b/lib/snmp/test/snmp_agent_SUITE.erl
@@ -944,25 +944,26 @@ end_per_testcase(Case, Config) when is_list(Config) ->
%% already failed, we will want to get as much of the logs
%% as possible. So, set no timeout (infinity) and let the
%% test framework take care of things...
- DisplayLogTimeout =
- case ?config(tc_status, Config) of
- ok ->
- ?SECS(30);
- _ ->
- infinity
+ %% But also, *don't* bother with this unless the test case
+ %% has failed!
+ case ?config(tc_status, Config) of
+ ok ->
+ ok;
+ _ ->
+ To = ?SECS(30),
+ Flag = process_flag(trap_exit, true),
+ Pid = spawn_link(fun() -> display_log(Config), exit(normal) end),
+ receive
+ {'EXIT', Pid, _} ->
+ process_flag(trap_exit, Flag),
+ ok
+ after To ->
+ ?WPRINT("Display Log process fail to complete in time"
+ "(~w msec): kill it", [To]),
+ process_flag(trap_exit, Flag),
+ exit(Pid, kill)
+ end
end,
- Flag = process_flag(trap_exit, true),
- Pid = spawn_link(fun() -> display_log(Config), exit(normal) end),
- receive
- {'EXIT', Pid, _} ->
- process_flag(trap_exit, Flag),
- ok
- after DisplayLogTimeout ->
- ?WPRINT("Display Log process fail to complete in time (~w msec): "
- "kill it", [DisplayLogTimeout]),
- process_flag(trap_exit, Flag),
- exit(Pid, kill)
- end,
Result = end_per_testcase1(Case, Config),
@@ -1443,6 +1444,7 @@ msd_varm_mib_start(X) ->
msm_varm_mib_start(X) ->
%% <CONDITIONAL-SKIP>
+ %% This is a bit radioactive but...
Skippable = [win32],
Condition = fun() -> ?OS_BASED_SKIP(Skippable) end,
?NON_PC_TC_MAYBE_SKIP(X, Condition),
@@ -6307,27 +6309,34 @@ otp_1131_3(X) ->
%% Montavista Linux looks like a Debian distro (/etc/issue)
LinuxVersionVerify =
fun() ->
- case os:cmd("uname -m") of
+ case string:to_lower(os:cmd("uname -m")) of
"ppc" ++ _ ->
case file:read_file_info("/etc/issue") of
{ok, _} ->
- case os:cmd("grep -i montavista /etc/issue") of
- Info when (is_list(Info) andalso
- (length(Info) > 0)) ->
+ case string:to_lower(
+ os:cmd("grep -i montavista /etc/issue")) of
+ "montavista" ++ _ ->
case os:version() of
{2, 6, 10} ->
+ ?IPRINT("(PPC Linux) kernel version check: "
+ "{2, 6, 10} => SKIP"),
true;
- _ ->
+ V ->
+ ?IPRINT("(PPC Linux) kernel version check: "
+ "~p != {2, 6, 10} => *NO* SKIP", [V]),
false
end;
_ -> % Maybe plain Debian or Ubuntu
+ ?IPRINT("(PPC Linux) Not MontaVista => *NO* SKIP"),
false
end;
_ ->
%% Not a Debian based distro
+ ?IPRINT("(PPC Linux) Unknown distro => *NO* SKIP"),
false
end;
_ ->
+ ?IPRINT("(Linux) Not PPC => *NO* SKIP"),
false
end
end,
diff --git a/lib/snmp/test/snmp_agent_mibs_SUITE.erl b/lib/snmp/test/snmp_agent_mibs_SUITE.erl
index 150e015554..39946ba7d1 100644
--- a/lib/snmp/test/snmp_agent_mibs_SUITE.erl
+++ b/lib/snmp/test/snmp_agent_mibs_SUITE.erl
@@ -172,29 +172,28 @@ init_per_testcase2(size_check_ets2_bad_file1, Config) when is_list(Config) ->
%% Create a bad file
ok = file:write_file(join(DbDir, "snmpa_symbolic_store.db"),
"calvin and hoppes play chess"),
+ Factor = ?config(snmp_factor, Config),
+ ct:timetrap(?MINS(1 + (Factor div 2))),
Config;
init_per_testcase2(size_check_ets3_bad_file1, Config) when is_list(Config) ->
DbDir = ?config(db_dir, Config),
%% Create a bad file
ok = file:write_file(join(DbDir, "snmpa_symbolic_store.db"),
"calvin and hoppes play chess"),
+ Factor = ?config(snmp_factor, Config),
+ ct:timetrap(?MINS(1 + (Factor div 2))),
Config;
init_per_testcase2(size_check_mnesia, Config) when is_list(Config) ->
+ Factor = ?config(snmp_factor, Config),
+ ct:timetrap(?MINS(1 + (Factor div 2))),
Config;
init_per_testcase2(cache_test, Config) when is_list(Config) ->
- Min = timer:minutes(5),
- Timeout =
- case lists:keysearch(tc_timeout, 1, Config) of
- {value, {tc_timeout, TcTimeout}} when TcTimeout < Min ->
- Min;
- {value, {tc_timeout, TcTimeout}} ->
- TcTimeout;
- _ ->
- Min
- end,
- Dog = test_server:timetrap(Timeout),
- [{watchdog, Dog} | Config];
+ Factor = ?config(snmp_factor, Config),
+ ct:timetrap(?MINS(5 + (Factor div 2))),
+ Config;
init_per_testcase2(_Case, Config) when is_list(Config) ->
+ Factor = ?config(snmp_factor, Config),
+ ct:timetrap(?MINS(1 + (Factor div 3))),
Config.
@@ -220,6 +219,10 @@ end_per_testcase1(_Case, Config) when is_list(Config) ->
start_and_stop(suite) -> [];
start_and_stop(Config) when is_list(Config) ->
+ tc_try(start_and_start,
+ fun() -> do_start_and_stop(Config) end).
+
+do_start_and_stop(_Config) ->
Prio = normal,
Verbosity = trace,
@@ -238,6 +241,10 @@ start_and_stop(Config) when is_list(Config) ->
load_unload(suite) -> [];
load_unload(Config) when is_list(Config) ->
+ tc_try(load_unload,
+ fun() -> do_load_unload(Config) end).
+
+do_load_unload(Config) ->
?DBG("load_unload -> start", []),
Prio = normal,
@@ -365,49 +372,8 @@ do_size_check(Name, Config) ->
do_size_check(Name, Init, Config).
do_size_check(Name, Init, Config) ->
- Pre = fun() ->
- {ok, Node} = ?ALIB:start_node(unique(Name)),
- ok = run_on(Node, Init),
- Node
- end,
- Case = fun(Node) ->
- monitor_node(Node, true),
- Pid = spawn_link(Node, fun() -> do_size_check(Config) end),
- receive
- {nodedown, Node} = N ->
- exit(N);
- {'EXIT', Pid, normal} ->
- monitor_node(Node, false),
- ok;
- {'EXIT', Pid, ok} ->
- monitor_node(Node, false),
- ok;
- {'EXIT', Pid, Reason} ->
- monitor_node(Node, false),
- exit(Reason)
- end
- end,
- Post = fun({Node, _}) ->
- ?STOP_NODE(Node)
- end,
- ?TC_TRY(Name, Pre, Case, Post).
+ tc_try(Name, Init, fun() -> do_size_check(Config) end).
-run_on(Node, F) when is_atom(Node) andalso is_function(F, 0) ->
- monitor_node(Node, true),
- Pid = spawn_link(Node, F),
- receive
- {nodedown, Node} = N ->
- exit(N);
- {'EXIT', Pid, normal} ->
- monitor_node(Node, false),
- ok;
- {'EXIT', Pid, Reason} ->
- monitor_node(Node, false),
- Reason
- end.
-
-unique(PreName) ->
- list_to_atom(?F("~w_~w", [PreName, erlang:system_time(millisecond)])).
do_size_check(Config) ->
?IPRINT("do_size_check -> start with"
@@ -465,6 +431,10 @@ do_size_check(Config) ->
me_lookup(suite) -> [];
me_lookup(Config) when is_list(Config) ->
+ tc_try(me_lookup,
+ fun() -> do_me_lookup(Config) end).
+
+do_me_lookup(Config) ->
Prio = normal,
Verbosity = trace,
MibDir = ?config(data_dir, Config),
@@ -518,6 +488,10 @@ me_lookup(Config) when is_list(Config) ->
which_mib(suite) -> [];
which_mib(Config) when is_list(Config) ->
+ tc_try(which_mib,
+ fun() -> do_which_mib(Config) end).
+
+do_which_mib(Config) ->
Prio = normal,
Verbosity = trace,
MibDir = ?config(data_dir, Config),
@@ -574,7 +548,11 @@ which_mib(Config) when is_list(Config) ->
cache_test(suite) -> [];
cache_test(Config) when is_list(Config) ->
- ?DBG("cache_test -> start", []),
+ tc_try(cache_test,
+ fun() -> do_cache_test(Config) end).
+
+do_cache_test(Config) ->
+ ?DBG("do_cache_test -> start", []),
Prio = normal,
Verbosity = trace,
MibStorage = [{module, snmpa_mib_storage_ets}],
@@ -660,15 +638,17 @@ walk(MibsPid) ->
do_walk(MibsPid, Oid, MibView) ->
- io:format("do_walk -> entry with"
- "~n Oid: ~p"
- "~n", [Oid]),
+ ?IPRINT("do_walk -> entry with"
+ "~n Oid: ~p"
+ "~n", [Oid]),
case snmpa_mib:next(MibsPid, Oid, MibView) of
{table, _, _, #me{oid = Oid}} ->
+ ?IPRINT("do_walk -> done for table (~p)", [Oid]),
ok;
{table, _, _, #me{oid = Next}} ->
do_walk(MibsPid, Next, MibView);
{variable, #me{oid = Oid}, _} ->
+ ?IPRINT("do_walk -> done for variable (~p)", [Oid]),
ok;
{variable, #me{oid = Next}, _} ->
do_walk(MibsPid, Next, MibView)
@@ -897,6 +877,65 @@ mib_storage() ->
[{module, snmpa_mib_storage_ets}].
+%% --
+
+tc_try(Name, TC) ->
+ tc_try(Name, fun() -> ok end, TC).
+
+tc_try(Name, Init, TC)
+ when is_atom(Name) andalso is_function(Init, 0) andalso is_function(TC, 0) ->
+ Pre = fun() ->
+ {ok, Node} = ?ALIB:start_node(unique(Name)),
+ ok = run_on(Node, Init),
+ Node
+ end,
+ Case = fun(Node) ->
+ monitor_node(Node, true),
+ Pid = spawn_link(Node, TC),
+ receive
+ {nodedown, Node} = N ->
+ exit(N);
+ {'EXIT', Pid, normal} ->
+ monitor_node(Node, false),
+ ok;
+ {'EXIT', Pid, ok} ->
+ monitor_node(Node, false),
+ ok;
+ {'EXIT', Pid, Reason} ->
+ monitor_node(Node, false),
+ exit(Reason)
+ end
+ end,
+ Post = fun(Node) ->
+ monitor_node(Node, true),
+ ?NPRINT("try stop node ~p", [Node]),
+ ?STOP_NODE(Node),
+ receive
+ {nodedown, Node} ->
+ ?NPRINT("node ~p stopped", [Node]),
+ ok
+ end
+ end,
+ ?TC_TRY(Name, Pre, Case, Post).
+
+run_on(Node, F) when is_atom(Node) andalso is_function(F, 0) ->
+ monitor_node(Node, true),
+ Pid = spawn_link(Node, F),
+ receive
+ {nodedown, Node} = N ->
+ exit(N);
+ {'EXIT', Pid, normal} ->
+ monitor_node(Node, false),
+ ok;
+ {'EXIT', Pid, Reason} ->
+ monitor_node(Node, false),
+ Reason
+ end.
+
+unique(PreName) ->
+ list_to_atom(?F("~w_~w", [PreName, erlang:system_time(millisecond)])).
+
+
%% --
display_memory_usage(MibsPid) ->
diff --git a/lib/snmp/test/snmp_manager_SUITE.erl b/lib/snmp/test/snmp_manager_SUITE.erl
index 6e695f5ddf..6cc0b0ebde 100644
--- a/lib/snmp/test/snmp_manager_SUITE.erl
+++ b/lib/snmp/test/snmp_manager_SUITE.erl
@@ -68,34 +68,24 @@
info/1,
usm_priv_aes/1,
- simple_sync_get2/1,
simple_sync_get3/1,
- simple_async_get2/1,
simple_async_get3/1,
- simple_sync_get_next2/1,
simple_sync_get_next3/1,
- simple_async_get_next2/1,
simple_async_get_next3_cbp_def/1,
simple_async_get_next3_cbp_temp/1,
simple_async_get_next3_cbp_perm/1,
- simple_sync_set2/1,
simple_sync_set3/1,
- simple_async_set2/1,
simple_async_set3_cbp_def/1,
simple_async_set3_cbp_temp/1,
simple_async_set3_cbp_perm/1,
- simple_sync_get_bulk2/1,
simple_sync_get_bulk3/1,
- simple_async_get_bulk2/1,
simple_async_get_bulk3_cbp_def/1,
simple_async_get_bulk3_cbp_temp/1,
simple_async_get_bulk3_cbp_perm/1,
- misc_async2/1,
-
discovery/1,
trap1/1,
@@ -206,8 +196,7 @@ groups() ->
{group, get_tests},
{group, get_next_tests},
{group, set_tests},
- {group, bulk_tests},
- {group, misc_request_tests}
+ {group, bulk_tests}
]
},
{request_tests_mt, [],
@@ -215,23 +204,18 @@ groups() ->
{group, get_tests},
{group, get_next_tests},
{group, set_tests},
- {group, bulk_tests},
- {group, misc_request_tests}
+ {group, bulk_tests}
]
},
{get_tests, [],
[
- simple_sync_get2,
simple_sync_get3,
- simple_async_get2,
simple_async_get3
]
},
{get_next_tests, [],
[
- simple_sync_get_next2,
simple_sync_get_next3,
- simple_async_get_next2,
simple_async_get_next3_cbp_def,
simple_async_get_next3_cbp_temp,
simple_async_get_next3_cbp_perm
@@ -239,9 +223,7 @@ groups() ->
},
{set_tests, [],
[
- simple_sync_set2,
simple_sync_set3,
- simple_async_set2,
simple_async_set3_cbp_def,
simple_async_set3_cbp_temp,
simple_async_set3_cbp_perm
@@ -249,19 +231,12 @@ groups() ->
},
{bulk_tests, [],
[
- simple_sync_get_bulk2,
simple_sync_get_bulk3,
- simple_async_get_bulk2,
simple_async_get_bulk3_cbp_def,
simple_async_get_bulk3_cbp_temp,
simple_async_get_bulk3_cbp_perm
]
},
- {misc_request_tests, [],
- [
- misc_async2
- ]
- },
{event_tests, [],
[
trap1,
@@ -315,16 +290,11 @@ ipv6_tests() ->
[
register_agent_old,
simple_sync_get_next3,
- simple_async_get2,
simple_sync_get3,
- simple_async_get_next2,
simple_sync_set3,
- simple_async_set2,
- simple_sync_get_bulk2,
simple_async_get_bulk3_cbp_def,
simple_async_get_bulk3_cbp_temp,
simple_async_get_bulk3_cbp_perm,
- misc_async2,
inform1,
inform_swarm_cbp_def,
inform_swarm_cbp_temp,
@@ -529,18 +499,16 @@ init_per_testcase2(Case, Config) ->
Family = proplists:get_value(ipfamily, Config, inet),
+ Factor = ?config(snmp_factor, Config),
TO = case Case of
inform3 ->
- ?MINS(2);
+ ?MINS(2 + (Factor div 2));
InformSwarm when (InformSwarm =:= inform_swarm_cbp_def) orelse
(InformSwarm =:= inform_swarm_cbp_temp) orelse
(InformSwarm =:= inform_swarm_cbp_perm) ->
- case ?config(snmp_factor, Config) of
- N when is_integer(N) -> ?MINS(2*N);
- _ -> ?MINS(2)
- end;
+ ?MINS(1 + Factor);
_ ->
- ?MINS(1)
+ ?MINS(1 + (Factor div 2))
end,
?IPRINT("Set test case timetrap: ~p", [TO]),
ct:timetrap(TO),
@@ -564,15 +532,6 @@ init_per_testcase2(Case, Config) ->
init_per_testcase3(Case, Config) ->
ApiCases02 =
[
- simple_sync_get2,
- simple_async_get2,
- simple_sync_get_next2,
- simple_async_get_next2,
- simple_sync_set2,
- simple_async_set2,
- simple_sync_get_bulk2,
- simple_async_get_bulk2,
- misc_async2,
otp8395_1
],
ApiCases03 =
@@ -702,15 +661,6 @@ end_per_testcase(Case, Config) when is_list(Config) ->
end_per_testcase2(Case, Config) ->
ApiCases02 =
[
- simple_sync_get2,
- simple_async_get2,
- simple_sync_get_next2,
- simple_async_get_next2,
- simple_sync_set2,
- simple_async_set2,
- simple_sync_get_bulk2,
- simple_async_get_bulk2,
- misc_async2,
otp8395_1
],
ApiCases03 =
@@ -1159,20 +1109,15 @@ notify_started02(Config) when is_list(Config) ->
notify_started02_cond(Config) ->
LinuxVersionVerify =
fun() ->
- case os:cmd("uname -m") of
- "i686" ++ _ ->
- case os:version() of
- {2, 6, Rev} when Rev >= 16 ->
- false;
- {2, Min, _} when Min > 6 ->
- false;
- {Maj, _, _} when Maj > 2 ->
- false;
- _ ->
- true
- end;
- _ ->
- false
+ case os:version() of
+ V when V > {2, 6, 16} ->
+ ?IPRINT("(Linux) kernel version check: "
+ "~p > {2, 6, 16} => *NO* SKIP", [V]),
+ false;
+ V ->
+ ?IPRINT("(Linux) kernel version check: "
+ "~p =< {2, 6, 16} => *SKIP*", [V]),
+ true
end
end,
Skippable = [{unix, [{linux, LinuxVersionVerify}]}],
@@ -2142,25 +2087,40 @@ do_register_agent3([ManagerNode], Config) ->
%%======================================================================
-simple_sync_get2(doc) ->
- ["Simple sync get-request - Version 2 API (TargetName)"];
-simple_sync_get2(suite) -> [];
-simple_sync_get2(Config) when is_list(Config) ->
- ?TC_TRY(simple_sync_get2,
- fun() -> do_simple_sync_get2(Config) end).
+simple_sync_get3(doc) ->
+ ["Simple sync get-request - Version 3 API (TargetName and send-opts)"];
+simple_sync_get3(suite) -> [];
+simple_sync_get3(Config) when is_list(Config) ->
+ ?TC_TRY(simple_sync_get3,
+ fun() -> do_simple_sync_get3(Config) end).
-do_simple_sync_get2(Config) ->
+do_simple_sync_get3(Config) ->
?IPRINT("starting with Config: "
"~n ~p", [Config]),
+ Self = self(),
+ Msg = simple_sync_get3,
+ Fun = fun() -> Self ! Msg end,
+ Extra = {?SNMPM_EXTRA_INFO_TAG, Fun},
+ SendOpts =
+ [
+ {extra, Extra}
+ ],
Get = fun(Node, TargetName, Oids) ->
- mgr_user_sync_get(Node, TargetName, Oids)
- end,
- PostVerify = fun() -> ok end,
- Res = do_simple_sync_get2(Config, Get, PostVerify),
+ mgr_user_sync_get2(Node, TargetName, Oids, SendOpts)
+ end,
+ PostVerify =
+ fun() ->
+ receive
+ Msg ->
+ ok
+ end
+ end,
+ Res = do_simple_sync_get3(Config, Get, PostVerify),
display_log(Config),
Res.
-do_simple_sync_get2(Config, Get, PostVerify) ->
+
+do_simple_sync_get3(Config, Get, PostVerify) ->
?IPRINT("starting with Config: "
"~n ~p", [Config]),
@@ -2169,15 +2129,15 @@ do_simple_sync_get2(Config, Get, PostVerify) ->
?IPRINT("issue get-request without loading the mib"),
Oids1 = [?sysObjectID_instance, ?sysDescr_instance, ?sysUpTime_instance],
- ?line ok = do_simple_sync_get2(Node, TargetName, Oids1, Get, PostVerify),
+ ?line ok = do_simple_sync_get3(Node, TargetName, Oids1, Get, PostVerify),
?IPRINT("issue get-request after first loading the mibs"),
?line ok = mgr_user_load_mib(Node, std_mib()),
Oids2 = [[sysObjectID, 0], [sysDescr, 0], [sysUpTime, 0]],
- ?line ok = do_simple_sync_get2(Node, TargetName, Oids2, Get, PostVerify),
+ ?line ok = do_simple_sync_get3(Node, TargetName, Oids2, Get, PostVerify),
ok.
-do_simple_sync_get2(Node, TargetName, Oids, Get, PostVerify)
+do_simple_sync_get3(Node, TargetName, Oids, Get, PostVerify)
when is_function(Get, 3) andalso is_function(PostVerify, 0) ->
?line {ok, Reply, _Rem} = Get(Node, TargetName, Oids),
@@ -2213,43 +2173,6 @@ do_simple_sync_get2(Node, TargetName, Oids, Get, PostVerify)
%%======================================================================
-simple_sync_get3(doc) ->
- ["Simple sync get-request - Version 3 API (TargetName and send-opts)"];
-simple_sync_get3(suite) -> [];
-simple_sync_get3(Config) when is_list(Config) ->
- ?TC_TRY(simple_sync_get3,
- fun() -> do_simple_sync_get3(Config) end).
-
-do_simple_sync_get3(Config) ->
- ?IPRINT("starting with Config: "
- "~n ~p", [Config]),
- Self = self(),
- Msg = simple_sync_get3,
- Fun = fun() -> Self ! Msg end,
- Extra = {?SNMPM_EXTRA_INFO_TAG, Fun},
- SendOpts =
- [
- {extra, Extra}
- ],
- Get = fun(Node, TargetName, Oids) ->
- mgr_user_sync_get2(Node, TargetName, Oids, SendOpts)
- end,
- PostVerify =
- fun() ->
- receive
- Msg ->
- ok
- end
- end,
- Res = do_simple_sync_get2(Config, Get, PostVerify),
- display_log(Config),
- Res.
-
-
-
-
-%%======================================================================
-
sag_verify({noError, 0, _Vbs}, any) ->
?IPRINT("verified [any]"),
ok;
@@ -2283,35 +2206,46 @@ sag_verify_vbs([Vb|_], [E|_]) ->
%%======================================================================
-simple_async_get2(doc) ->
- ["Simple (async) get-request - Version 2 API (TargetName)"];
-simple_async_get2(suite) -> [];
-simple_async_get2(Config) when is_list(Config) ->
- ?TC_TRY(simple_async_get2,
- fun() -> do_simple_async_get2(Config) end).
+simple_async_get3(doc) ->
+ ["Simple (async) get-request - Version 3 API (TargetName and send-opts)"];
+simple_async_get3(suite) -> [];
+simple_async_get3(Config) when is_list(Config) ->
+ ?TC_TRY(simple_async_get3,
+ fun() -> do_simple_async_get3(Config) end).
-do_simple_async_get2(Config) ->
+do_simple_async_get3(Config) ->
?IPRINT("starting with Config: "
"~n ~p", [Config]),
MgrNode = ?config(manager_node, Config),
AgentNode = ?config(agent_node, Config),
TargetName = ?config(manager_agent_target_name, Config),
- Get = fun(Oids) -> async_g_exec2(MgrNode, TargetName, Oids) end,
- PostVerify = fun(Res) -> Res end,
- do_simple_async_sync_get2(Config, MgrNode, AgentNode, Get, PostVerify),
+ Self = self(),
+ Msg = simple_async_get3,
+ Fun = fun() -> Self ! Msg end,
+ Extra = {?SNMPM_EXTRA_INFO_TAG, Fun},
+ SendOpts =
+ [
+ {extra, Extra}
+ ],
+ Get = fun(Oids) -> async_g_exec3(MgrNode, TargetName, Oids, SendOpts) end,
+ PostVerify = fun(ok) -> receive Msg -> ok end;
+ (Error) -> Error
+ end,
+ Res = do_simple_async_sync_get3(Config, MgrNode, AgentNode,
+ Get, PostVerify),
display_log(Config),
- ok.
+ Res.
-do_simple_async_sync_get2(Config, MgrNode, AgentNode, Get, PostVerify) ->
+do_simple_async_sync_get3(Config, MgrNode, AgentNode, Get, PostVerify) ->
?line ok = mgr_user_load_mib(MgrNode, std_mib()),
Test2Mib = test2_mib(Config),
?line ok = mgr_user_load_mib(MgrNode, Test2Mib),
?line ok = agent_load_mib(AgentNode, Test2Mib),
- do_simple_async_sync_get2(fun() -> mgr_info(MgrNode) end,
+ do_simple_async_sync_get3(fun() -> mgr_info(MgrNode) end,
fun() -> agent_info(AgentNode) end,
Get, PostVerify).
-do_simple_async_sync_get2(MgrInfo, AgentInfo, Get, PostVerify)
+do_simple_async_sync_get3(MgrInfo, AgentInfo, Get, PostVerify)
when is_function(MgrInfo, 0) andalso
is_function(AgentInfo, 0) andalso
is_function(Get, 1) andalso
@@ -2365,41 +2299,6 @@ do_simple_async_sync_get2(MgrInfo, AgentInfo, Get, PostVerify)
ok.
-async_g_exec2(Node, TargetName, Oids) ->
- mgr_user_async_get(Node, TargetName, Oids).
-
-
-%%======================================================================
-
-simple_async_get3(doc) ->
- ["Simple (async) get-request - Version 3 API (TargetName and send-opts)"];
-simple_async_get3(suite) -> [];
-simple_async_get3(Config) when is_list(Config) ->
- ?TC_TRY(simple_async_get3,
- fun() -> do_simple_async_get3(Config) end).
-
-do_simple_async_get3(Config) ->
- ?IPRINT("starting with Config: "
- "~n ~p", [Config]),
- MgrNode = ?config(manager_node, Config),
- AgentNode = ?config(agent_node, Config),
- TargetName = ?config(manager_agent_target_name, Config),
- Self = self(),
- Msg = simple_async_get3,
- Fun = fun() -> Self ! Msg end,
- Extra = {?SNMPM_EXTRA_INFO_TAG, Fun},
- SendOpts =
- [
- {extra, Extra}
- ],
- Get = fun(Oids) -> async_g_exec3(MgrNode, TargetName, Oids, SendOpts) end,
- PostVerify = fun(ok) -> receive Msg -> ok end;
- (Error) -> Error
- end,
- Res = do_simple_async_sync_get2(Config, MgrNode, AgentNode, Get, PostVerify),
- display_log(Config),
- Res.
-
async_g_exec3(Node, TargetName, Oids, SendOpts) ->
mgr_user_async_get2(Node, TargetName, Oids, SendOpts).
@@ -2440,27 +2339,35 @@ check_ssgn_vbs([Vb|_], [E|_]) ->
%%======================================================================
-simple_sync_get_next2(doc) ->
- ["Simple (sync) get_next-request - Version 2 API (TargetName)"];
-simple_sync_get_next2(suite) -> [];
-simple_sync_get_next2(Config) when is_list(Config) ->
- ?TC_TRY(simple_sync_get_next2,
- fun() -> do_simple_sync_get_next2(Config) end).
-
-do_simple_sync_get_next2(Config) ->
+simple_sync_get_next3(doc) ->
+ ["Simple (sync) get_next-request - "
+ "Version 3 API (TargetName with send-opts)"];
+simple_sync_get_next3(suite) -> [];
+simple_sync_get_next3(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ put(tname, ssgn3),
?IPRINT("starting with Config: "
"~n ~p", [Config]),
-
+ Self = self(),
+ Msg = simple_sync_get_next3,
+ Fun = fun() -> Self ! Msg end,
+ Extra = {?SNMPM_EXTRA_INFO_TAG, Fun},
+ SendOpts =
+ [
+ {extra, Extra}
+ ],
GetNext = fun(Node, TargetName, Oids) ->
- mgr_user_sync_get_next(Node, TargetName, Oids)
+ mgr_user_sync_get_next2(Node, TargetName, Oids, SendOpts)
end,
- PostVerify = fun(Res) -> Res end,
- Res = do_simple_sync_get_next2(Config, GetNext, PostVerify),
+ PostVerify = fun(ok) -> receive Msg -> ok end;
+ (Error) -> Error
+ end,
+ do_simple_sync_get_next3(Config, GetNext, PostVerify),
display_log(Config),
- Res.
+ ok.
-do_simple_sync_get_next2(Config, GetNext, PostVerify)
+do_simple_sync_get_next3(Config, GetNext, PostVerify)
when is_function(GetNext, 3) andalso is_function(PostVerify, 1) ->
MgrNode = ?config(manager_node, Config),
@@ -2550,7 +2457,6 @@ do_simple_sync_get_next2(Config, GetNext, PostVerify)
GetNext, PostVerify),
ok.
-
do_simple_get_next(N, Node, TargetName, Oids, Verify, GetNext, PostVerify) ->
?IPRINT("issue get-next command ~w", [N]),
case GetNext(Node, TargetName, Oids) of
@@ -2565,46 +2471,36 @@ do_simple_get_next(N, Node, TargetName, Oids, Verify, GetNext, PostVerify) ->
end.
+
%%======================================================================
-simple_sync_get_next3(doc) ->
- ["Simple (sync) get_next-request - "
+simple_async_get_next3_cbp_def(doc) ->
+ ["Simple (async) get_next-request - "
"Version 3 API (TargetName with send-opts)"];
-simple_sync_get_next3(suite) -> [];
-simple_sync_get_next3(Config) when is_list(Config) ->
- process_flag(trap_exit, true),
- put(tname, ssgn3),
- ?IPRINT("starting with Config: "
- "~n ~p", [Config]),
- Self = self(),
- Msg = simple_sync_get_next3,
- Fun = fun() -> Self ! Msg end,
- Extra = {?SNMPM_EXTRA_INFO_TAG, Fun},
- SendOpts =
- [
- {extra, Extra}
- ],
- GetNext = fun(Node, TargetName, Oids) ->
- mgr_user_sync_get_next2(Node, TargetName, Oids, SendOpts)
- end,
- PostVerify = fun(ok) -> receive Msg -> ok end;
- (Error) -> Error
- end,
- do_simple_sync_get_next2(Config, GetNext, PostVerify),
- display_log(Config),
- ok.
+simple_async_get_next3_cbp_def(suite) -> [];
+simple_async_get_next3_cbp_def(Config) when is_list(Config) ->
+ simple_async_get_next3(ssgn2_cbp_def, Config).
+simple_async_get_next3_cbp_temp(doc) ->
+ ["Simple (async) get_next-request - "
+ "Version 3 API (TargetName with send-opts)"];
+simple_async_get_next3_cbp_temp(suite) -> [];
+simple_async_get_next3_cbp_temp(Config) when is_list(Config) ->
+ simple_async_get_next3(ssgn2_cbp_temp, Config).
-%%======================================================================
+simple_async_get_next3_cbp_perm(doc) ->
+ ["Simple (async) get_next-request - "
+ "Version 3 API (TargetName with send-opts)"];
+simple_async_get_next3_cbp_perm(suite) -> [];
+simple_async_get_next3_cbp_perm(Config) when is_list(Config) ->
+ simple_async_get_next3(ssgn2_cbp_perm, Config).
-simple_async_get_next2(doc) ->
- ["Simple (async) get_next-request - Version 2 API (TargetName)"];
-simple_async_get_next2(suite) -> [];
-simple_async_get_next2(Config) when is_list(Config) ->
- ?TC_TRY(simple_async_get_next2,
- fun() -> do_simple_async_get_next2(Config) end).
+simple_async_get_next3(Case, Config) when is_list(Config) ->
+ ?TC_TRY(Case,
+ fun() -> do_simple_async_get_next3(Config) end).
-do_simple_async_get_next2(Config) ->
+do_simple_async_get_next3(Config) ->
+ %% process_flag(trap_exit, true),
?IPRINT("starting with Config: "
"~n ~p", [Config]),
@@ -2616,15 +2512,28 @@ do_simple_async_get_next2(Config) ->
Test2Mib = test2_mib(Config),
?line ok = mgr_user_load_mib(MgrNode, Test2Mib),
?line ok = agent_load_mib(AgentNode, Test2Mib),
+
+ Self = self(),
+ Msg = simple_async_get_next3,
+ Fun = fun() -> Self ! Msg end,
+ Extra = {?SNMPM_EXTRA_INFO_TAG, Fun},
+ SendOpts =
+ [
+ {extra, Extra}
+ ],
+
GetNext = fun(Oids) ->
- async_gn_exec2(MgrNode, TargetName, Oids)
+ async_gn_exec3(MgrNode, TargetName, Oids, SendOpts)
end,
- PostVerify = fun(Res) -> Res end,
- Res = do_simple_async_get_next2(MgrNode, AgentNode, GetNext, PostVerify),
+ PostVerify = fun(ok) -> receive Msg -> ok end;
+ (Error) -> Error
+ end,
+
+ Res = do_simple_async_get_next3(MgrNode, AgentNode, GetNext, PostVerify),
display_log(Config),
Res.
-do_simple_async_get_next2(MgrNode, AgentNode, GetNext, PostVerify)
+do_simple_async_get_next3(MgrNode, AgentNode, GetNext, PostVerify)
when is_function(GetNext, 1) andalso is_function(PostVerify, 1) ->
?line {ok, [TCnt2|_]} = mgr_user_name_to_oid(MgrNode, tCnt2),
?line {ok, [TGenErr1|_]} = mgr_user_name_to_oid(MgrNode, tGenErr1),
@@ -2708,71 +2617,6 @@ do_simple_async_get_next2(MgrNode, AgentNode, GetNext, PostVerify)
ok.
-async_gn_exec2(Node, TargetName, Oids) ->
- mgr_user_async_get_next(Node, TargetName, Oids).
-
-
-%%======================================================================
-
-simple_async_get_next3_cbp_def(doc) ->
- ["Simple (async) get_next-request - "
- "Version 3 API (TargetName with send-opts)"];
-simple_async_get_next3_cbp_def(suite) -> [];
-simple_async_get_next3_cbp_def(Config) when is_list(Config) ->
- simple_async_get_next3(ssgn2_cbp_def, Config).
-
-simple_async_get_next3_cbp_temp(doc) ->
- ["Simple (async) get_next-request - "
- "Version 3 API (TargetName with send-opts)"];
-simple_async_get_next3_cbp_temp(suite) -> [];
-simple_async_get_next3_cbp_temp(Config) when is_list(Config) ->
- simple_async_get_next3(ssgn2_cbp_temp, Config).
-
-simple_async_get_next3_cbp_perm(doc) ->
- ["Simple (async) get_next-request - "
- "Version 3 API (TargetName with send-opts)"];
-simple_async_get_next3_cbp_perm(suite) -> [];
-simple_async_get_next3_cbp_perm(Config) when is_list(Config) ->
- simple_async_get_next3(ssgn2_cbp_perm, Config).
-
-simple_async_get_next3(Case, Config) when is_list(Config) ->
- ?TC_TRY(Case,
- fun() -> do_simple_async_get_next3(Config) end).
-
-do_simple_async_get_next3(Config) ->
- %% process_flag(trap_exit, true),
- ?IPRINT("starting with Config: "
- "~n ~p", [Config]),
-
- MgrNode = ?config(manager_node, Config),
- AgentNode = ?config(agent_node, Config),
- TargetName = ?config(manager_agent_target_name, Config),
-
- ?line ok = mgr_user_load_mib(MgrNode, std_mib()),
- Test2Mib = test2_mib(Config),
- ?line ok = mgr_user_load_mib(MgrNode, Test2Mib),
- ?line ok = agent_load_mib(AgentNode, Test2Mib),
-
- Self = self(),
- Msg = simple_async_get_next3,
- Fun = fun() -> Self ! Msg end,
- Extra = {?SNMPM_EXTRA_INFO_TAG, Fun},
- SendOpts =
- [
- {extra, Extra}
- ],
-
- GetNext = fun(Oids) ->
- async_gn_exec3(MgrNode, TargetName, Oids, SendOpts)
- end,
- PostVerify = fun(ok) -> receive Msg -> ok end;
- (Error) -> Error
- end,
-
- Res = do_simple_async_get_next2(MgrNode, AgentNode, GetNext, PostVerify),
- display_log(Config),
- Res.
-
async_gn_exec3(Node, TargetName, Oids, SendOpts) ->
mgr_user_async_get_next2(Node, TargetName, Oids, SendOpts).
@@ -2792,27 +2636,36 @@ value_of_vavs([{_Oid, Val}|VAVs], Acc) ->
%%======================================================================
-simple_sync_set2(doc) ->
- ["Simple (sync) set-request - Version 2 API (TargetName)"];
-simple_sync_set2(suite) -> [];
-simple_sync_set2(Config) when is_list(Config) ->
- ?TC_TRY(simple_sync_set2,
- fun() -> do_simple_sync_set2(Config) end).
+simple_sync_set3(doc) ->
+ ["Simple (sync) set-request - Version 3 API (TargetName with send-opts)"];
+simple_sync_set3(suite) -> [];
+simple_sync_set3(Config) when is_list(Config) ->
+ ?TC_TRY(simple_sync_set3,
+ fun() -> do_simple_sync_set3(Config) end).
-do_simple_sync_set2(Config) ->
+do_simple_sync_set3(Config) ->
?IPRINT("starting with Config: "
"~n ~p", [Config]),
+ Self = self(),
+ Msg = simple_sync_set3,
+ Fun = fun() -> Self ! Msg end,
+ Extra = {?SNMPM_EXTRA_INFO_TAG, Fun},
+ SendOpts =
+ [
+ {extra, Extra}
+ ],
+
Set = fun(Node, TargetName, VAVs) ->
- mgr_user_sync_set(Node, TargetName, VAVs)
+ mgr_user_sync_set2(Node, TargetName, VAVs, SendOpts)
end,
- PostVerify = fun() -> ok end,
+ PostVerify = fun() -> receive Msg -> ok end end,
- Res = do_simple_sync_set2(Config, Set, PostVerify),
+ Res = do_simple_sync_set3(Config, Set, PostVerify),
display_log(Config),
Res.
-do_simple_sync_set2(Config, Set, PostVerify)
+do_simple_sync_set3(Config, Set, PostVerify)
when is_function(Set, 3) andalso is_function(PostVerify, 0) ->
Node = ?config(manager_node, Config),
@@ -2825,7 +2678,7 @@ do_simple_sync_set2(Config, Set, PostVerify)
{?sysName_instance, s, Val11},
{?sysLocation_instance, s, Val12}
],
- ?line ok = do_simple_set2(Node, TargetName, VAVs1, Set, PostVerify),
+ ?line ok = do_simple_set3(Node, TargetName, VAVs1, Set, PostVerify),
?IPRINT("issue set-request after first loading the mibs"),
?line ok = mgr_user_load_mib(Node, std_mib()),
@@ -2835,10 +2688,10 @@ do_simple_sync_set2(Config, Set, PostVerify)
{[sysName, 0], Val21},
{[sysLocation, 0], Val22}
],
- ?line ok = do_simple_set2(Node, TargetName, VAVs2, Set, PostVerify),
+ ?line ok = do_simple_set3(Node, TargetName, VAVs2, Set, PostVerify),
ok.
-do_simple_set2(Node, TargetName, VAVs, Set, PostVerify) ->
+do_simple_set3(Node, TargetName, VAVs, Set, PostVerify) ->
[SysName, SysLoc] = value_of_vavs(VAVs),
?line {ok, Reply, _Rem} = Set(Node, TargetName, VAVs),
@@ -2866,38 +2719,6 @@ do_simple_set2(Node, TargetName, VAVs, Set, PostVerify) ->
%%======================================================================
-simple_sync_set3(doc) ->
- ["Simple (sync) set-request - Version 3 API (TargetName with send-opts)"];
-simple_sync_set3(suite) -> [];
-simple_sync_set3(Config) when is_list(Config) ->
- ?TC_TRY(simple_sync_set3,
- fun() -> do_simple_sync_set3(Config) end).
-
-do_simple_sync_set3(Config) ->
- ?IPRINT("starting with Config: "
- "~n ~p", [Config]),
-
- Self = self(),
- Msg = simple_sync_set3,
- Fun = fun() -> Self ! Msg end,
- Extra = {?SNMPM_EXTRA_INFO_TAG, Fun},
- SendOpts =
- [
- {extra, Extra}
- ],
-
- Set = fun(Node, TargetName, VAVs) ->
- mgr_user_sync_set2(Node, TargetName, VAVs, SendOpts)
- end,
- PostVerify = fun() -> receive Msg -> ok end end,
-
- Res = do_simple_sync_set2(Config, Set, PostVerify),
- display_log(Config),
- Res.
-
-
-%%======================================================================
-
sas_verify({noError, 0, _Vbs}, any) ->
?IPRINT("verified [any]"),
ok;
@@ -2930,17 +2751,31 @@ sas_verify_vbs([Vb|_], [E|_]) ->
%%======================================================================
-simple_async_set2(doc) ->
- ["Simple (async) set-request - Version 2 API (TargetName)"];
-simple_async_set2(suite) -> [];
-simple_async_set2(Config) when is_list(Config) ->
- ?TC_TRY(simple_async_set2,
- fun() -> do_simple_async_set2(Config) end).
+simple_async_set3_cbp_def(doc) ->
+ ["Simple (async) set-request - Version 3 API (TargetName with send-opts)"];
+simple_async_set3_cbp_def(suite) -> [];
+simple_async_set3_cbp_def(Config) when is_list(Config) ->
+ simple_async_set3(sas3_cbp_def, Config).
+
+simple_async_set3_cbp_temp(doc) ->
+ ["Simple (async) set-request - Version 3 API (TargetName with send-opts)"];
+simple_async_set3_cbp_temp(suite) -> [];
+simple_async_set3_cbp_temp(Config) when is_list(Config) ->
+ simple_async_set3(sas3_cbp_temp, Config).
+
+simple_async_set3_cbp_perm(doc) ->
+ ["Simple (async) set-request - Version 3 API (TargetName with send-opts)"];
+simple_async_set3_cbp_perm(suite) -> [];
+simple_async_set3_cbp_perm(Config) when is_list(Config) ->
+ simple_async_set3(sas3_cbp_perm, Config).
+
+simple_async_set3(Case, Config) ->
+ ?TC_TRY(Case,
+ fun() -> do_simple_async_set3(Config) end).
-do_simple_async_set2(Config) ->
+do_simple_async_set3(Config) ->
?IPRINT("starting with Config: "
- "~n ~p"
- "~n", [Config]),
+ "~n ~p~n", [Config]),
MgrNode = ?config(manager_node, Config),
AgentNode = ?config(agent_node, Config),
@@ -2951,17 +2786,28 @@ do_simple_async_set2(Config) ->
?line ok = mgr_user_load_mib(MgrNode, Test2Mib),
?line ok = agent_load_mib(AgentNode, Test2Mib),
+ Self = self(),
+ Msg = simple_async_set3,
+ Fun = fun() -> Self ! Msg end,
+ Extra = {?SNMPM_EXTRA_INFO_TAG, Fun},
+ SendOpts =
+ [
+ {extra, Extra}
+ ],
+
Set =
fun(Oids) ->
- async_s_exec2(MgrNode, TargetName, Oids)
+ async_s_exec3(MgrNode, TargetName, Oids, SendOpts)
end,
- PostVerify = fun(Res) -> Res end,
+ PostVerify = fun(ok) -> receive Msg -> ok end;
+ (Res) -> Res
+ end,
- Res = do_simple_async_set2(MgrNode, AgentNode, Set, PostVerify),
+ Res = do_simple_async_set3(MgrNode, AgentNode, Set, PostVerify),
display_log(Config),
Res.
-do_simple_async_set2(MgrNode, AgentNode, Set, PostVerify) ->
+do_simple_async_set3(MgrNode, AgentNode, Set, PostVerify) ->
Requests =
[
{1,
@@ -3005,68 +2851,6 @@ do_simple_async_set2(MgrNode, AgentNode, Set, PostVerify) ->
ok.
-async_s_exec2(Node, TargetName, VAVs) ->
- mgr_user_async_set(Node, TargetName, VAVs).
-
-
-%%======================================================================
-
-simple_async_set3_cbp_def(doc) ->
- ["Simple (async) set-request - Version 3 API (TargetName with send-opts)"];
-simple_async_set3_cbp_def(suite) -> [];
-simple_async_set3_cbp_def(Config) when is_list(Config) ->
- simple_async_set3(sas3_cbp_def, Config).
-
-simple_async_set3_cbp_temp(doc) ->
- ["Simple (async) set-request - Version 3 API (TargetName with send-opts)"];
-simple_async_set3_cbp_temp(suite) -> [];
-simple_async_set3_cbp_temp(Config) when is_list(Config) ->
- simple_async_set3(sas3_cbp_temp, Config).
-
-simple_async_set3_cbp_perm(doc) ->
- ["Simple (async) set-request - Version 3 API (TargetName with send-opts)"];
-simple_async_set3_cbp_perm(suite) -> [];
-simple_async_set3_cbp_perm(Config) when is_list(Config) ->
- simple_async_set3(sas3_cbp_perm, Config).
-
-simple_async_set3(Case, Config) ->
- ?TC_TRY(Case,
- fun() -> do_simple_async_set3(Config) end).
-
-do_simple_async_set3(Config) ->
- ?IPRINT("starting with Config: "
- "~n ~p~n", [Config]),
-
- MgrNode = ?config(manager_node, Config),
- AgentNode = ?config(agent_node, Config),
- TargetName = ?config(manager_agent_target_name, Config),
-
- ?line ok = mgr_user_load_mib(MgrNode, std_mib()),
- Test2Mib = test2_mib(Config),
- ?line ok = mgr_user_load_mib(MgrNode, Test2Mib),
- ?line ok = agent_load_mib(AgentNode, Test2Mib),
-
- Self = self(),
- Msg = simple_async_set3,
- Fun = fun() -> Self ! Msg end,
- Extra = {?SNMPM_EXTRA_INFO_TAG, Fun},
- SendOpts =
- [
- {extra, Extra}
- ],
-
- Set =
- fun(Oids) ->
- async_s_exec3(MgrNode, TargetName, Oids, SendOpts)
- end,
- PostVerify = fun(ok) -> receive Msg -> ok end;
- (Res) -> Res
- end,
-
- Res = do_simple_async_set2(MgrNode, AgentNode, Set, PostVerify),
- display_log(Config),
- Res.
-
async_s_exec3(Node, TargetName, VAVs, SendOpts) ->
mgr_user_async_set2(Node, TargetName, VAVs, SendOpts).
@@ -3110,14 +2894,15 @@ check_ssgb_vbs([R|_], [E|_]) ->
%%======================================================================
-simple_sync_get_bulk2(doc) ->
- ["Simple (sync) get_bulk-request - Version 2 API (TargetName)"];
-simple_sync_get_bulk2(suite) -> [];
-simple_sync_get_bulk2(Config) when is_list(Config) ->
- ?TC_TRY(simple_sync_get_bulk2,
- fun() -> do_simple_sync_get_bulk2(Config) end).
+simple_sync_get_bulk3(doc) ->
+ ["Simple (sync) get_bulk-request - "
+ "Version 3 API (TargetName with send-opts)"];
+simple_sync_get_bulk3(suite) -> [];
+simple_sync_get_bulk3(Config) when is_list(Config) ->
+ ?TC_TRY(simple_sync_get_bulk3,
+ fun() -> do_simple_sync_get_bulk3(Config) end).
-do_simple_sync_get_bulk2(Config) ->
+do_simple_sync_get_bulk3(Config) ->
?IPRINT("starting with Config: "
"~n ~p~n", [Config]),
@@ -3125,32 +2910,43 @@ do_simple_sync_get_bulk2(Config) ->
AgentNode = ?config(agent_node, Config),
TargetName = ?config(manager_agent_target_name, Config),
+ Self = self(),
+ Msg = simple_async_set3,
+ Fun = fun() -> Self ! Msg end,
+ Extra = {?SNMPM_EXTRA_INFO_TAG, Fun},
+ SendOpts =
+ [
+ {extra, Extra}
+ ],
+
GetBulk =
fun(NonRep, MaxRep, Oids) ->
- mgr_user_sync_get_bulk(MgrNode, TargetName,
- NonRep, MaxRep, Oids)
+ mgr_user_sync_get_bulk2(MgrNode, TargetName,
+ NonRep, MaxRep, Oids, SendOpts)
end,
- PostVerify = fun(Res) -> Res end,
+ PostVerify = fun(ok) -> receive Msg -> ok end;
+ (Res) -> Res
+ end,
- Res = do_simple_sync_get_bulk2(Config, MgrNode, AgentNode, GetBulk, PostVerify),
+ Res = do_simple_sync_get_bulk3(Config, MgrNode, AgentNode, GetBulk, PostVerify),
display_log(Config),
Res.
-do_simple_sync_get_bulk2(Config, MgrNode, AgentNode, GetBulk, PostVerify) ->
+do_simple_sync_get_bulk3(Config, MgrNode, AgentNode, GetBulk, PostVerify) ->
%% -- 1 --
- ?line ok = do_simple_get_bulk2(1,
+ ?line ok = do_simple_get_bulk3(1,
1, 1, [],
fun verify_ssgb_reply1/1,
GetBulk, PostVerify),
%% -- 2 --
- ?line ok = do_simple_get_bulk2(2,
+ ?line ok = do_simple_get_bulk3(2,
-1, 1, [],
fun verify_ssgb_reply1/1,
GetBulk, PostVerify),
%% -- 3 --
- ?line ok = do_simple_get_bulk2(3,
+ ?line ok = do_simple_get_bulk3(3,
-1, -1, [],
fun verify_ssgb_reply1/1,
GetBulk, PostVerify),
@@ -3160,12 +2956,12 @@ do_simple_sync_get_bulk2(Config, MgrNode, AgentNode, GetBulk, PostVerify) ->
VF04 = fun(X) ->
verify_ssgb_reply2(X, [?sysDescr_instance, endOfMibView])
end,
- ?line ok = do_simple_get_bulk2(4,
+ ?line ok = do_simple_get_bulk3(4,
2, 0, [[sysDescr],[1,3,7,1]], VF04,
GetBulk, PostVerify),
%% -- 5 --
- ?line ok = do_simple_get_bulk2(5,
+ ?line ok = do_simple_get_bulk3(5,
1, 2, [[sysDescr],[1,3,7,1]], VF04,
GetBulk, PostVerify),
@@ -3175,7 +2971,7 @@ do_simple_sync_get_bulk2(Config, MgrNode, AgentNode, GetBulk, PostVerify) ->
[?sysDescr_instance, endOfMibView,
?sysObjectID_instance, endOfMibView])
end,
- ?line ok = do_simple_get_bulk2(6,
+ ?line ok = do_simple_get_bulk3(6,
0, 2, [[sysDescr],[1,3,7,1]], VF06,
GetBulk, PostVerify),
@@ -3186,7 +2982,7 @@ do_simple_sync_get_bulk2(Config, MgrNode, AgentNode, GetBulk, PostVerify) ->
?sysDescr_instance, endOfMibView,
?sysObjectID_instance, endOfMibView])
end,
- ?line ok = do_simple_get_bulk2(7,
+ ?line ok = do_simple_get_bulk3(7,
2, 2,
[[sysDescr],[1,3,7,1],[sysDescr],[1,3,7,1]],
VF07,
@@ -3202,14 +2998,14 @@ do_simple_sync_get_bulk2(Config, MgrNode, AgentNode, GetBulk, PostVerify) ->
[?sysDescr_instance,
?sysDescr_instance])
end,
- ?line ok = do_simple_get_bulk2(8,
+ ?line ok = do_simple_get_bulk3(8,
1, 2,
[[sysDescr],[sysDescr],[tTooBig]],
VF08,
GetBulk, PostVerify),
%% -- 9 --
- ?line ok = do_simple_get_bulk2(9,
+ ?line ok = do_simple_get_bulk3(9,
1, 12,
[[tDescr2], [sysDescr]],
fun verify_ssgb_reply1/1,
@@ -3223,7 +3019,7 @@ do_simple_sync_get_bulk2(Config, MgrNode, AgentNode, GetBulk, PostVerify) ->
{?tGenErr1, 'NULL'},
{?sysDescr, 'NULL'}])
end,
- ?line ok = do_simple_get_bulk2(10,
+ ?line ok = do_simple_get_bulk3(10,
2, 2,
[[sysDescr],
[sysObjectID],
@@ -3240,14 +3036,14 @@ do_simple_sync_get_bulk2(Config, MgrNode, AgentNode, GetBulk, PostVerify) ->
[{fl([TCnt2,2]), 100},
{fl([TCnt2,2]), endOfMibView}])
end,
- ?line ok = do_simple_get_bulk2(11,
+ ?line ok = do_simple_get_bulk3(11,
0, 2,
[[TCnt2, 1]], VF11,
GetBulk, PostVerify),
ok.
-do_simple_get_bulk2(N,
+do_simple_get_bulk3(N,
NonRep, MaxRep, Oids,
Verify, GetBulk, PostVerify)
when is_function(Verify, 1) andalso
@@ -3268,15 +3064,19 @@ do_simple_get_bulk2(N,
%%======================================================================
-simple_sync_get_bulk3(doc) ->
- ["Simple (sync) get_bulk-request - "
+simple_async_get_bulk3_cbp_def(doc) ->
+ ["Simple (async) get_bulk-request - "
"Version 3 API (TargetName with send-opts)"];
-simple_sync_get_bulk3(suite) -> [];
-simple_sync_get_bulk3(Config) when is_list(Config) ->
- ?TC_TRY(simple_sync_get_bulk3,
- fun() -> do_simple_sync_get_bulk3(Config) end).
+simple_async_get_bulk3_cbp_def(suite) -> [];
+simple_async_get_bulk3_cbp_def(Config) when is_list(Config) ->
+ simple_async_get_bulk3(sagb3_cbp_def, Config).
-do_simple_sync_get_bulk3(Config) ->
+simple_async_get_bulk3(Case, Config) ->
+ ?TC_TRY(Case,
+ fun() -> do_simple_async_get_bulk3(Config) end).
+
+do_simple_async_get_bulk3(Config) ->
+ process_flag(trap_exit, true),
?IPRINT("starting with Config: "
"~n ~p~n", [Config]),
@@ -3284,8 +3084,13 @@ do_simple_sync_get_bulk3(Config) ->
AgentNode = ?config(agent_node, Config),
TargetName = ?config(manager_agent_target_name, Config),
+ ?line ok = mgr_user_load_mib(MgrNode, std_mib()),
+ Test2Mib = test2_mib(Config),
+ ?line ok = mgr_user_load_mib(MgrNode, Test2Mib),
+ ?line ok = agent_load_mib(AgentNode, Test2Mib),
+
Self = self(),
- Msg = simple_async_set3,
+ Msg = simple_async_get_bulk3,
Fun = fun() -> Self ! Msg end,
Extra = {?SNMPM_EXTRA_INFO_TAG, Fun},
SendOpts =
@@ -3294,52 +3099,18 @@ do_simple_sync_get_bulk3(Config) ->
],
GetBulk =
- fun(NonRep, MaxRep, Oids) ->
- mgr_user_sync_get_bulk2(MgrNode, TargetName,
- NonRep, MaxRep, Oids, SendOpts)
+ fun(Data) ->
+ async_gb_exec3(MgrNode, TargetName, Data, SendOpts)
end,
- PostVerify = fun(ok) -> receive Msg -> ok end;
+ PostVerify = fun(ok) -> receive Msg -> ok end;
(Res) -> Res
end,
- Res = do_simple_sync_get_bulk2(Config, MgrNode, AgentNode, GetBulk, PostVerify),
+ Res = do_simple_async_get_bulk3(MgrNode, AgentNode, GetBulk, PostVerify),
display_log(Config),
Res.
-
-%%======================================================================
-
-simple_async_get_bulk2(doc) ->
- ["Simple (async) get_bulk-request - Version 2 API (TargetName)"];
-simple_async_get_bulk2(suite) -> [];
-simple_async_get_bulk2(Config) when is_list(Config) ->
- ?TC_TRY(simple_async_get_bulk2,
- fun() -> do_simple_async_get_bulk2(Config) end).
-
-do_simple_async_get_bulk2(Config) ->
- ?IPRINT("starting with Config: "
- "~p ~n", [Config]),
-
- MgrNode = ?config(manager_node, Config),
- AgentNode = ?config(agent_node, Config),
- TargetName = ?config(manager_agent_target_name, Config),
-
- ?line ok = mgr_user_load_mib(MgrNode, std_mib()),
- Test2Mib = test2_mib(Config),
- ?line ok = mgr_user_load_mib(MgrNode, Test2Mib),
- ?line ok = agent_load_mib(AgentNode, Test2Mib),
-
- GetBulk =
- fun(Data) ->
- async_gb_exec2(MgrNode, TargetName, Data)
- end,
- PostVerify = fun(Res) -> Res end,
-
- Res = do_simple_async_get_bulk2(MgrNode, AgentNode, GetBulk, PostVerify),
- display_log(Config),
- Res.
-
-do_simple_async_get_bulk2(MgrNode, AgentNode, GetBulk, PostVerify) ->
+do_simple_async_get_bulk3(MgrNode, AgentNode, GetBulk, PostVerify) ->
%% We re-use the verification functions from the ssgb test-case
VF04 = fun(X) ->
PostVerify(
@@ -3465,58 +3236,6 @@ do_simple_async_get_bulk2(MgrNode, AgentNode, GetBulk, PostVerify) ->
ok.
-async_gb_exec2(Node, TargetName, {NR, MR, Oids}) ->
- mgr_user_async_get_bulk(Node, TargetName, NR, MR, Oids).
-
-
-%%======================================================================
-
-simple_async_get_bulk3_cbp_def(doc) ->
- ["Simple (async) get_bulk-request - "
- "Version 3 API (TargetName with send-opts)"];
-simple_async_get_bulk3_cbp_def(suite) -> [];
-simple_async_get_bulk3_cbp_def(Config) when is_list(Config) ->
- simple_async_get_bulk3(sagb3_cbp_def, Config).
-
-simple_async_get_bulk3(Case, Config) ->
- ?TC_TRY(Case,
- fun() -> do_simple_async_get_bulk3(Config) end).
-
-do_simple_async_get_bulk3(Config) ->
- process_flag(trap_exit, true),
- ?IPRINT("starting with Config: "
- "~n ~p~n", [Config]),
-
- MgrNode = ?config(manager_node, Config),
- AgentNode = ?config(agent_node, Config),
- TargetName = ?config(manager_agent_target_name, Config),
-
- ?line ok = mgr_user_load_mib(MgrNode, std_mib()),
- Test2Mib = test2_mib(Config),
- ?line ok = mgr_user_load_mib(MgrNode, Test2Mib),
- ?line ok = agent_load_mib(AgentNode, Test2Mib),
-
- Self = self(),
- Msg = simple_async_get_bulk3,
- Fun = fun() -> Self ! Msg end,
- Extra = {?SNMPM_EXTRA_INFO_TAG, Fun},
- SendOpts =
- [
- {extra, Extra}
- ],
-
- GetBulk =
- fun(Data) ->
- async_gb_exec3(MgrNode, TargetName, Data, SendOpts)
- end,
- PostVerify = fun(ok) -> receive Msg -> ok end;
- (Res) -> Res
- end,
-
- Res = do_simple_async_get_bulk2(MgrNode, AgentNode, GetBulk, PostVerify),
- display_log(Config),
- Res.
-
async_gb_exec3(Node, TargetName, {NR, MR, Oids}, SendOpts) ->
mgr_user_async_get_bulk2(Node, TargetName, NR, MR, Oids, SendOpts).
@@ -3543,203 +3262,6 @@ simple_async_get_bulk3_cbp_perm(Config) when is_list(Config) ->
%%======================================================================
-misc_async2(doc) ->
- ["Misc (async) request(s) - Version 2 API (TargetName)"];
-misc_async2(suite) -> [];
-misc_async2(Config) when is_list(Config) ->
- ?TC_TRY(misc_async2,
- fun() -> do_misc_async2(Config) end).
-
-do_misc_async2(Config) ->
- ?IPRINT("starting with Config: "
- "~n ~p"
- "~n", [Config]),
-
- MgrNode = ?config(manager_node, Config),
- AgentNode = ?config(agent_node, Config),
- TargetName = ?config(manager_agent_target_name, Config),
-
- ?line ok = mgr_user_load_mib(MgrNode, std_mib()),
- Test2Mib = test2_mib(Config),
- ?line ok = mgr_user_load_mib(MgrNode, Test2Mib),
- ?line ok = agent_load_mib(AgentNode, Test2Mib),
-
- ExecG = fun(Data) ->
- async_g_exec2(MgrNode, TargetName, Data)
- end,
-
- ExecGN = fun(Data) ->
- async_gn_exec2(MgrNode, TargetName, Data)
- end,
-
- ExecS = fun(Data) ->
- async_s_exec2(MgrNode, TargetName, Data)
- end,
-
- ExecGB = fun(Data) ->
- async_gb_exec2(MgrNode, TargetName, Data)
- end,
-
- ?line {ok, [TCnt2|_]} = mgr_user_name_to_oid(MgrNode, tCnt2),
- ?line {ok, [TGenErr1|_]} = mgr_user_name_to_oid(MgrNode, tGenErr1),
- ?line {ok, [TGenErr2|_]} = mgr_user_name_to_oid(MgrNode, tGenErr2),
- ?line {ok, [TGenErr3|_]} = mgr_user_name_to_oid(MgrNode, tGenErr3),
- ?line {ok, [TTooBig|_]} = mgr_user_name_to_oid(MgrNode, tTooBig),
-
- Requests =
- [
- { 1,
- [?sysObjectID_instance],
- ExecG,
- fun(X) ->
- sag_verify(X, [?sysObjectID_instance])
- end
- },
- { 2,
- {1, 1, []},
- ExecGB,
- fun verify_ssgb_reply1/1},
- { 3,
- {-1, 1, []},
- ExecGB,
- fun verify_ssgb_reply1/1},
- { 4,
- [{?sysLocation_instance, s, "Stockholm"},
- {?sysName_instance, s, "Arne Anka"}],
- ExecS,
- fun(X) ->
- sas_verify(X, [?sysLocation_instance, ?sysName_instance])
- end},
- { 5,
- [[sysDescr], [1,3,7,1]],
- ExecGN,
- fun(X) ->
- verify_ssgn_reply1(X, [?sysDescr_instance, endOfMibView])
- end},
- { 6,
- [[sysObjectID, 0], [sysDescr, 0], [sysUpTime, 0]],
- ExecG,
- fun(X) ->
- sag_verify(X, [?sysObjectID_instance,
- ?sysDescr_instance,
- ?sysUpTime_instance])
- end},
- { 7,
- [TGenErr2],
- ExecGN,
- fun(X) ->
- verify_ssgn_reply2(X, {genErr, 1, [TGenErr2]})
- end},
- { 8,
- {2, 0, [[sysDescr],[1,3,7,1]]},
- ExecGB,
- fun(X) ->
- verify_ssgb_reply2(X, [?sysDescr_instance, endOfMibView])
- end},
- { 9,
- {1, 2, [[sysDescr],[1,3,7,1]]},
- ExecGB,
- fun(X) ->
- verify_ssgb_reply2(X, [?sysDescr_instance, endOfMibView])
- end},
- {10,
- [TGenErr1],
- ExecGN,
- fun(X) ->
- verify_ssgn_reply2(X, {genErr, 1, [TGenErr1]})
- end},
- {11,
- {0, 2, [[sysDescr],[1,3,7,1]]},
- ExecGB,
- fun(X) ->
- verify_ssgb_reply2(X,
- [?sysDescr_instance, endOfMibView,
- ?sysObjectID_instance, endOfMibView])
- end},
- {12,
- [{[sysName, 0], "Gothenburg"},
- {[sysLocation, 0], "Sune Anka"}],
- ExecS,
- fun(X) ->
- sas_verify(X, [?sysName_instance, ?sysLocation_instance])
- end},
- {13,
- {2, 2, [[sysDescr],[1,3,7,1],[sysDescr],[1,3,7,1]]},
- ExecGB,
- fun(X) ->
- verify_ssgb_reply2(X,
- [?sysDescr_instance, endOfMibView,
- ?sysDescr_instance, endOfMibView,
- ?sysObjectID_instance, endOfMibView])
- end},
- {14,
- {1, 2, [[sysDescr],[sysDescr],[tTooBig]]},
- ExecGB,
- fun(X) ->
- verify_ssgb_reply2(X,
- [?sysDescr_instance,
- ?sysDescr_instance])
- end},
- {15,
- {1, 12, [[tDescr2], [sysDescr]]},
- ExecGB,
- fun verify_ssgb_reply1/1},
- {16,
- {2, 2, [[sysDescr],[sysObjectID], [tGenErr1],[sysDescr]]},
- ExecGB,
- fun(X) ->
- verify_ssgb_reply3(X,
- [{?sysDescr, 'NULL'},
- {?sysObjectID, 'NULL'},
- {?tGenErr1, 'NULL'},
- {?sysDescr, 'NULL'}])
- end},
- {17,
- [[sysDescr], TGenErr3],
- ExecGN,
- fun(X) ->
- verify_ssgn_reply2(X, {genErr, 2, [TGenErr3]})
- end},
- {18,
- {0, 2, [[TCnt2, 1]]},
- ExecGB,
- fun(X) ->
- verify_ssgb_reply2(X,
- [{fl([TCnt2,2]), 100},
- {fl([TCnt2,2]), endOfMibView}])
- end},
- {19,
- [TTooBig],
- ExecGN,
- fun(X) ->
- verify_ssgn_reply2(X, {tooBig, 0, []})
- end},
- {20,
- [TTooBig],
- ExecGN,
- fun(X) ->
- verify_ssgn_reply2(X, {tooBig, 0, []})
- end}
- ],
-
- ?IPRINT("manager info when starting test: "
- "~n ~p", [mgr_info(MgrNode)]),
- ?IPRINT("agent info when starting test: "
- "~n ~p", [agent_info(AgentNode)]),
-
- ?line ok = async_exec(Requests, []),
-
- ?IPRINT("manager info when ending test: "
- "~n ~p", [mgr_info(MgrNode)]),
- ?IPRINT("agent info when ending test: "
- "~n ~p", [agent_info(AgentNode)]),
-
- display_log(Config),
- ok.
-
-
-%%======================================================================
-
discovery(suite) -> [];
discovery(Config) when is_list(Config) ->
?SKIP(not_yet_implemented).
@@ -4764,7 +4286,8 @@ do_inform_swarm(Config) ->
"~p ~n", [Config]),
MgrNode = ?config(manager_node, Config),
- AgentNode = ?config(agent_node, Config),
+ AgentNode = ?config(agent_node, Config),
+ Factor = ?config(snmp_factor, Config),
?line ok = mgr_user_load_mib(MgrNode, snmpv2_mib()),
Test2Mib = test2_mib(Config),
@@ -4776,7 +4299,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,
+ NumInforms = 2000 div Factor,
Collector = self(),
@@ -4796,8 +4319,8 @@ do_inform_swarm(Config) ->
{{inform2_tag1, N}, Collector},
"standard inform",
[]),
- %% Sleep some [(N div 10)*100 ms]
- %% every tenth notification
+ %% Sleep 1000 ms every 100th notif
+ %% Sleep 100 ms every 10th notif
if
N rem 100 == 0 ->
Sleep = 1000,
@@ -4836,11 +4359,11 @@ do_inform_swarm(Config) ->
Commands =
[
- {1, "Manager and agent info at start of test", Cmd1},
- {2, "Send notifcation(s) from agent", Cmd2},
- {3, "Await send-ack(s)/inform(s)/response(s)", Cmd3},
- {4, "Sleep some time (1 sec)", Cmd4},
- {5, "Manager and agent info after test completion", Cmd1}
+ {1, "Manager and agent info at start of test", Cmd1},
+ {2, ?F("Send ~p notifcation(s) from agent", [NumInforms]), Cmd2},
+ {3, "Await send-ack(s)/inform(s)/response(s)", Cmd3},
+ {4, "Sleep some time (1 sec)", Cmd4},
+ {5, "Manager and agent info after test completion", Cmd1}
],
Res = command_handler(Commands),
@@ -4885,7 +4408,7 @@ inform_swarm_collector(N, SentAckCnt, RecvCnt, RespCnt, Timeout) ->
%% The manager has received the actual inform
{async_event, From, {inform, Pid, Inform}} ->
- ?IPRINT("received inform"),
+ ?IPRINT("received inform (~p of ~p)", [RecvCnt+1, N]),
case Inform of
{noError, 0, VBs} when is_list(VBs) ->
Pid ! {handle_inform_response, From},
@@ -5001,7 +4524,7 @@ otp8395_1(Config) when is_list(Config) ->
fun() -> do_otp8395_1(Config) end).
do_otp8395_1(Config) ->
- do_simple_sync_get2(Config).
+ do_simple_sync_get3(Config).
%%======================================================================
@@ -5707,16 +5230,16 @@ mgr_user_load_mib(Node, Mib) ->
%% mgr_user_sync_get(Node, Oids) ->
%% mgr_user_sync_get(Node, ?LOCALHOST(), ?AGENT_PORT, Oids).
-mgr_user_sync_get(Node, TargetName, Oids) when is_list(TargetName) ->
- rcall(Node, snmp_manager_user, sync_get, [TargetName, Oids]).
+%% mgr_user_sync_get(Node, TargetName, Oids) when is_list(TargetName) ->
+%% rcall(Node, snmp_manager_user, sync_get, [TargetName, Oids]).
mgr_user_sync_get2(Node, TargetName, Oids, SendOpts) when is_list(TargetName) ->
rcall(Node, snmp_manager_user, sync_get2, [TargetName, Oids, SendOpts]).
%% mgr_user_async_get(Node, Oids) ->
%% mgr_user_async_get(Node, ?LOCALHOST(), ?AGENT_PORT, Oids).
-mgr_user_async_get(Node, TargetName, Oids) when is_list(TargetName) ->
- rcall(Node, snmp_manager_user, async_get, [TargetName, Oids]).
+%% mgr_user_async_get(Node, TargetName, Oids) when is_list(TargetName) ->
+%% rcall(Node, snmp_manager_user, async_get, [TargetName, Oids]).
mgr_user_async_get2(Node, TargetName, Oids, SendOpts)
when is_list(TargetName) ->
@@ -5724,8 +5247,8 @@ mgr_user_async_get2(Node, TargetName, Oids, SendOpts)
%% mgr_user_sync_get_next(Node, Oids) ->
%% mgr_user_sync_get_next(Node, ?LOCALHOST(), ?AGENT_PORT, Oids).
-mgr_user_sync_get_next(Node, TargetName, Oids) when is_list(TargetName) ->
- rcall(Node, snmp_manager_user, sync_get_next, [TargetName, Oids]).
+%% mgr_user_sync_get_next(Node, TargetName, Oids) when is_list(TargetName) ->
+%% rcall(Node, snmp_manager_user, sync_get_next, [TargetName, Oids]).
mgr_user_sync_get_next2(Node, TargetName, Oids, SendOpts)
when is_list(TargetName) ->
@@ -5733,8 +5256,8 @@ mgr_user_sync_get_next2(Node, TargetName, Oids, SendOpts)
%% mgr_user_async_get_next(Node, Oids) ->
%% mgr_user_async_get_next(Node, ?LOCALHOST(), ?AGENT_PORT, Oids).
-mgr_user_async_get_next(Node, TargetName, Oids) when is_list(TargetName) ->
- rcall(Node, snmp_manager_user, async_get_next, [TargetName, Oids]).
+%% mgr_user_async_get_next(Node, TargetName, Oids) when is_list(TargetName) ->
+%% rcall(Node, snmp_manager_user, async_get_next, [TargetName, Oids]).
mgr_user_async_get_next2(Node, TargetName, Oids, SendOpts)
when is_list(TargetName) ->
@@ -5742,16 +5265,16 @@ mgr_user_async_get_next2(Node, TargetName, Oids, SendOpts)
%% mgr_user_sync_set(Node, VAV) ->
%% mgr_user_sync_set(Node, ?LOCALHOST(), ?AGENT_PORT, VAV).
-mgr_user_sync_set(Node, TargetName, VAV) when is_list(TargetName) ->
- rcall(Node, snmp_manager_user, sync_set, [TargetName, VAV]).
+%% mgr_user_sync_set(Node, TargetName, VAV) when is_list(TargetName) ->
+%% rcall(Node, snmp_manager_user, sync_set, [TargetName, VAV]).
mgr_user_sync_set2(Node, TargetName, VAV, SendOpts) when is_list(TargetName) ->
rcall(Node, snmp_manager_user, sync_set2, [TargetName, VAV, SendOpts]).
%% mgr_user_async_set(Node, VAV) ->
%% mgr_user_async_set(Node, ?LOCALHOST(), ?AGENT_PORT, VAV).
-mgr_user_async_set(Node, TargetName, VAV) when is_list(TargetName) ->
- rcall(Node, snmp_manager_user, async_set, [TargetName, VAV]).
+%% mgr_user_async_set(Node, TargetName, VAV) when is_list(TargetName) ->
+%% rcall(Node, snmp_manager_user, async_set, [TargetName, VAV]).
mgr_user_async_set2(Node, TargetName, VAV, SendOpts) when is_list(TargetName) ->
rcall(Node, snmp_manager_user, async_set2, [TargetName, VAV, SendOpts]).
@@ -5759,10 +5282,10 @@ mgr_user_async_set2(Node, TargetName, VAV, SendOpts) when is_list(TargetName) ->
%% mgr_user_sync_get_bulk(Node, NonRep, MaxRep, Oids) ->
%% mgr_user_sync_get_bulk(Node, ?LOCALHOST(), ?AGENT_PORT,
%% NonRep, MaxRep, Oids).
-mgr_user_sync_get_bulk(Node, TargetName, NonRep, MaxRep, Oids)
- when is_list(TargetName) ->
- rcall(Node, snmp_manager_user, sync_get_bulk,
- [TargetName, NonRep, MaxRep, Oids]).
+%% mgr_user_sync_get_bulk(Node, TargetName, NonRep, MaxRep, Oids)
+%% when is_list(TargetName) ->
+%% rcall(Node, snmp_manager_user, sync_get_bulk,
+%% [TargetName, NonRep, MaxRep, Oids]).
mgr_user_sync_get_bulk2(Node, TargetName, NonRep, MaxRep, Oids, SendOpts)
when is_list(TargetName) ->
@@ -5772,10 +5295,10 @@ mgr_user_sync_get_bulk2(Node, TargetName, NonRep, MaxRep, Oids, SendOpts)
%% mgr_user_async_get_bulk(Node, NonRep, MaxRep, Oids) ->
%% mgr_user_async_get_bulk(Node, ?LOCALHOST(), ?AGENT_PORT,
%% NonRep, MaxRep, Oids).
-mgr_user_async_get_bulk(Node, TargetName, NonRep, MaxRep, Oids)
- when is_list(TargetName) ->
- rcall(Node, snmp_manager_user, async_get_bulk,
- [TargetName, NonRep, MaxRep, Oids]).
+%% mgr_user_async_get_bulk(Node, TargetName, NonRep, MaxRep, Oids)
+%% when is_list(TargetName) ->
+%% rcall(Node, snmp_manager_user, async_get_bulk,
+%% [TargetName, NonRep, MaxRep, Oids]).
mgr_user_async_get_bulk2(Node, TargetName, NonRep, MaxRep, Oids, SendOpts)
when is_list(TargetName) ->
diff --git a/lib/snmp/test/snmp_manager_user.erl b/lib/snmp/test/snmp_manager_user.erl
index 409f87cf40..60a6844875 100644
--- a/lib/snmp/test/snmp_manager_user.erl
+++ b/lib/snmp/test/snmp_manager_user.erl
@@ -51,14 +51,14 @@
update_agent_info/3,
which_all_agents/0, which_own_agents/0,
load_mib/1, unload_mib/1,
- sync_get/1, sync_get/2, sync_get2/3,
- async_get/1, async_get/2, async_get2/3,
- sync_get_next/1, sync_get_next/2, sync_get_next2/3,
- async_get_next/1, async_get_next/2, async_get_next2/3,
- sync_set/1, sync_set/2, sync_set2/3,
- async_set/1, async_set/2, async_set2/3,
- sync_get_bulk/3, sync_get_bulk/4, sync_get_bulk2/5,
- async_get_bulk/3, async_get_bulk/4, async_get_bulk2/5,
+ sync_get2/3,
+ async_get2/3,
+ sync_get_next2/3,
+ async_get_next2/3,
+ sync_set2/3,
+ async_set2/3,
+ sync_get_bulk2/5,
+ async_get_bulk2/5,
name_to_oid/1, oid_to_name/1,
purify_oid/1
]).
@@ -159,90 +159,42 @@ unload_mib(Mib) ->
%% --
-sync_get(Oids) ->
- call({sync_get, Oids}).
-
-sync_get(TargetName, Oids) ->
- call({sync_get, TargetName, Oids}).
-
sync_get2(TargetName, Oids, SendOpts) ->
call({sync_get2, TargetName, Oids, SendOpts}).
%% --
-async_get(Oids) ->
- call({async_get, Oids}).
-
-async_get(TargetName, Oids) ->
- call({async_get, TargetName, Oids}).
-
async_get2(TargetName, Oids, SendOpts) ->
call({async_get2, TargetName, Oids, SendOpts}).
%% --
-sync_get_next(Oids) ->
- call({sync_get_next, Oids}).
-
-sync_get_next(TargetName, Oids) ->
- call({sync_get_next, TargetName, Oids}).
-
sync_get_next2(TargetName, Oids, SendOpts) ->
call({sync_get_next2, TargetName, Oids, SendOpts}).
%% --
-async_get_next(Oids) ->
- call({async_get_next, Oids}).
-
-async_get_next(TargetName, Oids) ->
- call({async_get_next, TargetName, Oids}).
-
async_get_next2(TargetName, Oids, SendOpts) ->
call({async_get_next2, TargetName, Oids, SendOpts}).
%% --
-sync_set(VAV) ->
- call({sync_set, VAV}).
-
-sync_set(TargetName, VAV) ->
- call({sync_set, TargetName, VAV}).
-
sync_set2(TargetName, VAV, SendOpts) ->
call({sync_set2, TargetName, VAV, SendOpts}).
%% --
-async_set(VAV) ->
- call({async_set, VAV}).
-
-async_set(TargetName, VAV) ->
- call({async_set, TargetName, VAV}).
-
async_set2(TargetName, VAV, SendOpts) ->
call({async_set2, TargetName, VAV, SendOpts}).
%% --
-sync_get_bulk(NonRep, MaxRep, Oids) ->
- call({sync_get_bulk, NonRep, MaxRep, Oids}).
-
-sync_get_bulk(TargetName, NonRep, MaxRep, Oids) ->
- call({sync_get_bulk, TargetName, NonRep, MaxRep, Oids}).
-
sync_get_bulk2(TargetName, NonRep, MaxRep, Oids, SendOpts) ->
call({sync_get_bulk2, TargetName, NonRep, MaxRep, Oids, SendOpts}).
%% --
-async_get_bulk(NonRep, MaxRep, Oids) ->
- call({async_get_bulk, NonRep, MaxRep, Oids}).
-
-async_get_bulk(TargetName, NonRep, MaxRep, Oids) ->
- call({async_get_bulk, TargetName, NonRep, MaxRep, Oids}).
-
async_get_bulk2(TargetName, NonRep, MaxRep, Oids, SendOpts) ->
call({async_get_bulk2, TargetName, NonRep, MaxRep, Oids, SendOpts}).
@@ -377,23 +329,6 @@ loop(#state{parent = Parent, id = Id} = S) ->
reply(From, Res, Ref),
loop(S);
- %% No agent specified, so send it to all of them
- {{sync_get, Oids}, From, Ref} ->
- d("loop -> received sync_get request "
- "(for every agent of this user)"),
- Res = [snmpm:sync_get(Id, TargetName, Oids) ||
- TargetName <- snmpm:which_agents(Id)],
- reply(From, Res, Ref),
- loop(S);
-
- {{sync_get, TargetName, Oids}, From, Ref} when is_list(TargetName) ->
- d("loop -> received sync_get request with"
- "~n TargetName: ~p"
- "~n Oids: ~p", [TargetName, Oids]),
- Res = snmpm:sync_get(Id, TargetName, Oids),
- reply(From, Res, Ref),
- loop(S);
-
%%
%% -- (async) get-request --
@@ -409,22 +344,6 @@ loop(#state{parent = Parent, id = Id} = S) ->
reply(From, Res, Ref),
loop(S);
- %% No agent specified, so send it to all of them
- {{async_get, Oids}, From, Ref} ->
- d("loop -> received async_get request"),
- Res = [snmpm:async_get(Id, TargetName, Oids) ||
- TargetName <- snmpm:which_agents(Id)],
- reply(From, Res, Ref),
- loop(S);
-
- {{async_get, TargetName, Oids}, From, Ref} when is_list(TargetName) ->
- d("loop -> received async_get request with"
- "~n TargetName: ~p"
- "~n Oids: ~p", [TargetName, Oids]),
- Res = snmpm:async_get(Id, TargetName, Oids),
- reply(From, Res, Ref),
- loop(S);
-
%%
%% -- (sync) get_next-request --
@@ -440,22 +359,6 @@ loop(#state{parent = Parent, id = Id} = S) ->
reply(From, Res, Ref),
loop(S);
- %% No agent specified, so send it to all of them
- {{sync_get_next, Oids}, From, Ref} ->
- d("loop -> received sync_get_next request"),
- Res = [snmpm:sync_get_next(Id, TargetName, Oids) ||
- TargetName <- snmpm:which_agents(Id)],
- reply(From, Res, Ref),
- loop(S);
-
- {{sync_get_next, TargetName, Oids}, From, Ref} when is_list(TargetName) ->
- d("loop -> received sync_get_next request with"
- "~n TargetName: ~p"
- "~n Oids: ~p", [TargetName, Oids]),
- Res = snmpm:sync_get_next(Id, TargetName, Oids),
- reply(From, Res, Ref),
- loop(S);
-
%%
%% -- (async) get_next-request --
@@ -471,22 +374,6 @@ loop(#state{parent = Parent, id = Id} = S) ->
reply(From, Res, Ref),
loop(S);
- %% No agent specified, so send it to all of them
- {{async_get_next, Oids}, From, Ref} ->
- d("loop -> received async_get_next request"),
- Res = [snmpm:async_get_next(Id, TargetName, Oids) ||
- TargetName <- snmpm:which_agents(Id)],
- reply(From, Res, Ref),
- loop(S);
-
- {{async_get_next, TargetName, Oids}, From, Ref} when is_list(TargetName) ->
- d("loop -> received async_get_next request with"
- "~n TargetName: ~p"
- "~n Oids: ~p", [TargetName, Oids]),
- Res = snmpm:async_get_next(Id, TargetName, Oids),
- reply(From, Res, Ref),
- loop(S);
-
%%
%% -- (sync) set-request --
@@ -502,19 +389,6 @@ loop(#state{parent = Parent, id = Id} = S) ->
reply(From, Res, Ref),
loop(S);
- {{sync_set, VAV}, From, Ref} ->
- d("loop -> received sync_set request"),
- Res = [snmpm:sync_set(Id, TargetName, VAV) ||
- TargetName <- snmpm:which_agents(Id)],
- reply(From, Res, Ref),
- loop(S);
-
- {{sync_set, TargetName, VAV}, From, Ref} when is_list(TargetName) ->
- d("loop -> received sync_set request"),
- Res = snmpm:sync_set(Id, TargetName, VAV),
- reply(From, Res, Ref),
- loop(S);
-
%%
%% -- (async) set-request --
@@ -530,19 +404,6 @@ loop(#state{parent = Parent, id = Id} = S) ->
reply(From, Res, Ref),
loop(S);
- {{async_set, VAV}, From, Ref} ->
- d("loop -> received async_set request"),
- Res = [snmpm:async_set(Id, TargetName, VAV) ||
- TargetName <- snmpm:which_agents(Id)],
- reply(From, Res, Ref),
- loop(S);
-
- {{async_set, TargetName, VAV}, From, Ref} when is_list(TargetName) ->
- d("loop -> received async_set request"),
- Res = snmpm:async_set(Id, TargetName, VAV),
- reply(From, Res, Ref),
- loop(S);
-
%%
%% -- (sync) get-bulk-request --
@@ -562,27 +423,6 @@ loop(#state{parent = Parent, id = Id} = S) ->
reply(From, Res, Ref),
loop(S);
- %% No agent specified, so send it to all of them
- {{sync_get_bulk, NonRep, MaxRep, Oids}, From, Ref} ->
- d("loop -> received sync_get_bulk request with"
- "~n NonRep: ~w"
- "~n MaxRep: ~w"
- "~n Oids: ~p", [NonRep, MaxRep, Oids]),
- Res = [snmpm:sync_get_bulk(Id, TargetName, NonRep, MaxRep, Oids) ||
- TargetName <- snmpm:which_agents(Id)],
- reply(From, Res, Ref),
- loop(S);
-
- {{sync_get_bulk, TargetName, NonRep, MaxRep, Oids}, From, Ref} when is_list(TargetName) ->
- d("loop -> received sync_get_bulk request with"
- "~n TargetName: ~p"
- "~n NonRep: ~w"
- "~n MaxRep: ~w"
- "~n Oids: ~p", [TargetName, NonRep, MaxRep, Oids]),
- Res = snmpm:sync_get_bulk(Id, TargetName, NonRep, MaxRep, Oids),
- reply(From, Res, Ref),
- loop(S);
-
%%
%% -- (async) get-bulk-request --
@@ -602,27 +442,6 @@ loop(#state{parent = Parent, id = Id} = S) ->
reply(From, Res, Ref),
loop(S);
- %% No agent specified, so send it to all of them
- {{async_get_bulk, NonRep, MaxRep, Oids}, From, Ref} ->
- d("loop -> received async_get_bulk request with"
- "~n NonRep: ~w"
- "~n MaxRep: ~w"
- "~n Oids: ~p", [NonRep, MaxRep, Oids]),
- Res = [snmpm:async_get_bulk(Id, TargetName, NonRep, MaxRep, Oids) ||
- TargetName <- snmpm:which_agents(Id)],
- reply(From, Res, Ref),
- loop(S);
-
- {{async_get_bulk, TargetName, NonRep, MaxRep, Oids}, From, Ref} when is_list(TargetName) ->
- d("loop -> received async_get_bulk request with"
- "~n TargetName: ~p"
- "~n NonRep: ~w"
- "~n MaxRep: ~w"
- "~n Oids: ~p", [TargetName, NonRep, MaxRep, Oids]),
- Res = snmpm:async_get_bulk(Id, TargetName, NonRep, MaxRep, Oids),
- reply(From, Res, Ref),
- loop(S);
-
%%
%% -- logical name translation --
diff --git a/lib/snmp/test/snmp_manager_user_SUITE.erl b/lib/snmp/test/snmp_manager_user_SUITE.erl
index eca0d8a4f9..bad746b29a 100644
--- a/lib/snmp/test/snmp_manager_user_SUITE.erl
+++ b/lib/snmp/test/snmp_manager_user_SUITE.erl
@@ -888,26 +888,17 @@ register_monitor_and_crash3(Conf) when is_list(Conf) ->
put(tname,rlac3),
%% <CONDITIONAL-SKIP>
- %% The point of this is to catch machines running
- %% SLES9 (2.6.5)
LinuxVersionVerify =
fun() ->
- case os:cmd("uname -m") of
- "i686" ++ _ ->
-%% io:format("found an i686 machine, "
-%% "now check version~n", []),
- case os:version() of
- {2, 6, Rev} when Rev >= 16 ->
- true;
- {2, Min, _} when Min > 6 ->
- true;
- {Maj, _, _} when Maj > 2 ->
- true;
- _ ->
- false
- end;
- _ ->
- true
+ case os:version() of
+ V when V > {2, 6, 16} ->
+ ?IPRINT("(Linux) kernel version check: "
+ "~p > {2, 6, 16} => *NO* SKIP", [V]),
+ false;
+ V ->
+ ?IPRINT("(Linux) kernel version check: "
+ "~p =< {2, 6, 16} => *SKIP*", [V]),
+ true
end
end,
Skippable = [{unix, [{linux, LinuxVersionVerify}]}],
@@ -922,10 +913,10 @@ register_monitor_and_crash3(Conf) when is_list(Conf) ->
write_manager_conf(ConfDir),
- Opts = [{server, [{verbosity, trace}]},
- {net_if, [{verbosity, trace}]},
+ Opts = [{server, [{verbosity, trace}]},
+ {net_if, [{verbosity, trace}]},
{note_store, [{verbosity, trace}]},
- {config, [{verbosity, trace}, {dir, ConfDir}, {db_dir, DbDir}]}],
+ {config, [{verbosity, trace}, {dir, ConfDir}, {db_dir, DbDir}]}],
?IPRINT("try starting manager"),
?line ok = snmpm:start_link(Opts),
diff --git a/lib/snmp/test/snmp_test_lib.erl b/lib/snmp/test/snmp_test_lib.erl
index fe6fef291d..76f424d25c 100644
--- a/lib/snmp/test/snmp_test_lib.erl
+++ b/lib/snmp/test/snmp_test_lib.erl
@@ -25,6 +25,7 @@
-export([tc_try/2, tc_try/3,
tc_try/4, tc_try/5]).
+-export([proxy_call/3]).
-export([hostname/0, hostname/1, localhost/0, localhost/1, os_type/0, sz/1,
display_suite_info/1]).
-export([non_pc_tc_maybe_skip/4,
@@ -257,6 +258,19 @@ tc_which_name() ->
%% Misc functions
%%
+proxy_call(F, Timeout, Default)
+ when is_function(F, 0) andalso is_integer(Timeout) andalso (Timeout > 0) ->
+ {P, M} = erlang:spawn_monitor(fun() -> exit(F()) end),
+ receive
+ {'DOWN', M, process, P, Reply} ->
+ Reply
+ after Timeout ->
+ erlang:demonitor(M, [flush]),
+ exit(P, kill),
+ Default
+ end.
+
+
hostname() ->
hostname(node()).
@@ -268,20 +282,6 @@ hostname(Node) ->
[]
end.
-%% localhost() ->
-%% {ok, Ip} = snmp_misc:ip(net_adm:localhost()),
-%% Ip.
-%% localhost(Family) ->
-%% {ok, Ip} = snmp_misc:ip(net_adm:localhost(), Family),
-%% Ip.
-
-%% localhost() ->
-%% {ok, Ip} = snmp_misc:ip(net_adm:localhost()),
-%% Ip.
-%% localhost(Family) ->
-%% {ok, Ip} = snmp_misc:ip(net_adm:localhost(), Family),
-%% Ip.
-
localhost() ->
localhost(inet).
@@ -467,6 +467,10 @@ os_base_skip(Skippable, OsFam, OsName) ->
case lists:keysearch(OsFam, 1, Skippable) of
{value, {OsFam, OsName}} ->
true;
+ {value, {OsFam, Check}} when is_function(Check, 0) ->
+ Check();
+ {value, {OsFam, Check}} when is_function(Check, 1) ->
+ Check(os:version());
{value, {OsFam, OsNames}} when is_list(OsNames) ->
%% OsNames is a list of:
%% [atom()|{atom(), function/0 | function/1}]
@@ -503,7 +507,7 @@ has_support_ipv6() ->
%% so for windows we need to use the old style...
old_has_support_ipv6();
_ ->
- socket:supports(ipv6) andalso has_valid_ipv6_address()
+ socket:is_supported(ipv6) andalso has_valid_ipv6_address()
end.
has_valid_ipv6_address() ->
@@ -619,11 +623,31 @@ old_is_ipv6_host(Hostname) ->
init_per_suite(Config) ->
+ ct:timetrap(minutes(2)),
+
+ try analyze_and_print_host_info() of
+ {Factor, HostInfo} when is_integer(Factor) ->
+ try maybe_skip(HostInfo) of
+ true ->
+ {skip, "Unstable host and/or os (or combo thererof)"};
+ false ->
+ snmp_test_global_sys_monitor:start(),
+ [{snmp_factor, Factor} | Config]
+ catch
+ throw:{skip, _} = SKIP ->
+ SKIP
+ end
+ catch
+ throw:{skip, _} = SKIP ->
+ SKIP
+ end.
+
+maybe_skip(HostInfo) ->
+
%% We have some crap machines that causes random test case failures
%% for no obvious reason. So, attempt to identify those without actually
%% checking for the host name...
- %% We have two "machines" we are checking for. Both are old installations
- %% running on really slow VMs (the host machines are old and tired).
+
LinuxVersionVerify =
fun(V) when (V > {3,6,11}) ->
false; % OK - No skip
@@ -634,8 +658,30 @@ init_per_suite(Config) ->
_ ->
false
end;
+ (V) when (V =:= {3,4,20}) ->
+ case string:trim(os:cmd("cat /etc/issue")) of
+ "Wind River Linux 5.0.1.0" ++ _ -> % *Old* Wind River => skip
+ true;
+ _ ->
+ false
+ end;
+ (V) when (V =:= {2,6,32}) ->
+ case string:trim(os:cmd("cat /etc/issue")) of
+ "Debian GNU/Linux 6.0 " ++ _ -> % Stone age Debian => Skip
+ true;
+ _ ->
+ false
+ end;
(V) when (V > {2,6,24}) ->
false; % OK - No skip
+ (V) when (V =:= {2,6,10}) ->
+ case string:trim(os:cmd("cat /etc/issue")) of
+ "MontaVista" ++ _ -> % Stone age MontaVista => Skip
+ %% The real problem is that the machine is *very* slow
+ true;
+ _ ->
+ false
+ end;
(_) ->
%% We are specifically checking for
%% a *really* old gento...
@@ -646,15 +692,28 @@ init_per_suite(Config) ->
true
end
end,
- COND = [{unix, [{linux, LinuxVersionVerify}]}],
- case os_based_skip(COND) of
- true ->
- {skip, "Unstable host and/or os (or combo thererof)"};
- false ->
- Factor = analyze_and_print_host_info(),
- snmp_test_global_sys_monitor:start(),
- [{snmp_factor, Factor} | Config]
- end.
+ DarwinVersionVerify =
+ fun(V) when (V > {9, 8, 0}) ->
+ %% This version is OK: No Skip
+ false;
+ (_V) ->
+ %% This version is *not* ok: Skip
+ true
+ end,
+ SkipWindowsOnVirtual =
+ fun() ->
+ SysMan = win_sys_info_lookup(system_manufacturer, HostInfo),
+ case string:to_lower(SysMan) of
+ "vmware" ++ _ ->
+ true;
+ _ ->
+ false
+ end
+ end,
+ COND = [{unix, [{linux, LinuxVersionVerify},
+ {darwin, DarwinVersionVerify}]},
+ {win32, SkipWindowsOnVirtual}],
+ os_based_skip(COND).
end_per_suite(Config) when is_list(Config) ->
@@ -795,7 +854,7 @@ skip(Reason, Module, Line) ->
%% when analyzing the test suite (results).
%% It also returns a "factor" that can be used when deciding
%% the load for some test cases. Such as run time or number of
-%% iteraions. This only works for some OSes.
+%% iterations. This only works for some OSes.
%%
%% We make some calculations on Linux, OpenBSD and FreeBSD.
%% On SunOS we always set the factor to 2 (just to be on the safe side)
@@ -819,6 +878,8 @@ analyze_and_print_host_info() ->
analyze_and_print_freebsd_host_info(Version);
{unix, netbsd} ->
analyze_and_print_netbsd_host_info(Version);
+ {unix, darwin} ->
+ analyze_and_print_darwin_host_info(Version);
{unix, sunos} ->
analyze_and_print_solaris_host_info(Version);
{win32, nt} ->
@@ -829,112 +890,288 @@ analyze_and_print_host_info() ->
"~n Version: ~p"
"~n Num Schedulers: ~s"
"~n", [OsFam, OsName, Version, str_num_schedulers()]),
- try erlang:system_info(schedulers) of
- 1 ->
- 10;
- 2 ->
- 5;
- N when (N =< 6) ->
- 2;
- _ ->
- 1
- catch
- _:_:_ ->
- 10
- end
+ {num_schedulers_to_factor(), []}
end.
-
-analyze_and_print_linux_host_info(Version) ->
+
+linux_which_distro(Version) ->
case file:read_file_info("/etc/issue") of
{ok, _} ->
- io:format("Linux: ~s"
- "~n ~s"
- "~n",
- [Version, string:trim(os:cmd("cat /etc/issue"))]);
+ case [string:trim(S) ||
+ S <- string:tokens(os:cmd("cat /etc/issue"), [$\n])] of
+ [DistroStr|_] ->
+ io:format("Linux: ~s"
+ "~n ~s"
+ "~n",
+ [Version, DistroStr]),
+ case DistroStr of
+ "Wind River Linux" ++ _ ->
+ wind_river;
+ "MontaVista" ++ _ ->
+ montavista;
+ "Yellow Dog" ++ _ ->
+ yellow_dog;
+ _ ->
+ other
+ end;
+ X ->
+ io:format("Linux: ~s"
+ "~n ~p"
+ "~n",
+ [Version, X]),
+ other
+ end;
_ ->
io:format("Linux: ~s"
- "~n", [Version])
- end,
+ "~n", [Version]),
+ other
+ end.
+
+analyze_and_print_linux_host_info(Version) ->
+ Distro =
+ case file:read_file_info("/etc/issue") of
+ {ok, _} ->
+ linux_which_distro(Version);
+ _ ->
+ io:format("Linux: ~s"
+ "~n", [Version]),
+ other
+ end,
Factor =
- case (catch linux_which_cpuinfo()) of
+ case (catch linux_which_cpuinfo(Distro)) of
{ok, {CPU, BogoMIPS}} ->
io:format("CPU: "
- "~n Model: ~s"
- "~n BogoMIPS: ~s"
- "~n", [CPU, BogoMIPS]),
- %% We first assume its a float, and if not try integer
- try list_to_float(string:trim(BogoMIPS)) of
- F when F > 1000 ->
+ "~n Model: ~s"
+ "~n BogoMIPS: ~w"
+ "~n Num Schedulers: ~s"
+ "~n", [CPU, BogoMIPS, str_num_schedulers()]),
+ if
+ (BogoMIPS > 20000) ->
1;
- F when F > 1000 ->
+ (BogoMIPS > 10000) ->
2;
- _ ->
- 3
- catch
- _:_:_ ->
- %%
- try list_to_integer(string:trim(BogoMIPS)) of
- I when I > 1000 ->
- 1;
- I when I > 1000 ->
- 2;
- _ ->
- 3
- catch
- _:_:_ ->
- 1
- end
+ (BogoMIPS > 5000) ->
+ 3;
+ (BogoMIPS > 2000) ->
+ 5;
+ (BogoMIPS > 1000) ->
+ 8;
+ true ->
+ 10
+ end;
+ {ok, CPU} ->
+ io:format("CPU: "
+ "~n Model: ~s"
+ "~n Num Schedulers: ~s"
+ "~n", [CPU, str_num_schedulers()]),
+ NumChed = erlang:system_info(schedulers),
+ if
+ (NumChed > 2) ->
+ 2;
+ true ->
+ 5
end;
_ ->
- 1
+ 5
end,
%% 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()]),
- Factor + AddFactor
+ {Factor + AddFactor, []}
catch
_:_:_ ->
io:format("TS Scale Factor: ~w~n", [timetrap_scale_factor()]),
- Factor
+ {Factor, []}
+ end.
+
+
+
+linux_cpuinfo_lookup(Key) when is_list(Key) ->
+ linux_info_lookup(Key, "/proc/cpuinfo").
+
+linux_cpuinfo_cpu() ->
+ case linux_cpuinfo_lookup("cpu") of
+ [Model] ->
+ Model;
+ _ ->
+ "-"
+ end.
+
+linux_cpuinfo_motherboard() ->
+ case linux_cpuinfo_lookup("motherboard") of
+ [MB] ->
+ MB;
+ _ ->
+ "-"
+ 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] ->
+ try bogomips_to_int(TBM)
+ catch
+ _:_:_ ->
+ "-"
+ end;
+ _ ->
+ "-"
+ end.
+
+bogomips_to_int(BM) ->
+ try list_to_float(BM) of
+ F ->
+ floor(F)
+ catch
+ _:_:_ ->
+ try list_to_integer(BM) of
+ I ->
+ I
+ catch
+ _:_:_ ->
+ throw(noinfo)
+ end
+ end.
+
+linux_cpuinfo_model() ->
+ case linux_cpuinfo_lookup("model") of
+ [M] ->
+ M;
+ _ ->
+ "-"
+ end.
+
+linux_cpuinfo_platform() ->
+ case linux_cpuinfo_lookup("platform") of
+ [P] ->
+ P;
+ _ ->
+ "-"
+ end.
+
+linux_cpuinfo_model_name() ->
+ case linux_cpuinfo_lookup("model name") of
+ [P|_] ->
+ P;
+ _ ->
+ "-"
end.
-linux_which_cpuinfo() ->
- %% Check for x86 (Intel or AMD)
+linux_cpuinfo_processor() ->
+ case linux_cpuinfo_lookup("Processor") of
+ [P] ->
+ P;
+ _ ->
+ "-"
+ end.
+
+
+linux_which_cpuinfo(montavista) ->
CPU =
- try [string:trim(S) || S <- string:tokens(os:cmd("grep \"model name\" /proc/cpuinfo"), [$:,$\n])] of
- ["model name", ModelName | _] ->
- ModelName;
- _ ->
+ case linux_cpuinfo_cpu() of
+ "-" ->
+ throw(noinfo);
+ Model ->
+ case linux_cpuinfo_motherboard() of
+ "-" ->
+ Model;
+ MB ->
+ Model ++ " (" ++ MB ++ ")"
+ end
+ end,
+ case linux_cpuinfo_bogomips() of
+ "-" ->
+ {ok, CPU};
+ BMips ->
+ {ok, {CPU, BMips}}
+ end;
+
+linux_which_cpuinfo(yellow_dog) ->
+ CPU =
+ case linux_cpuinfo_cpu() of
+ "-" ->
+ throw(noinfo);
+ Model ->
+ case linux_cpuinfo_motherboard() of
+ "-" ->
+ Model;
+ MB ->
+ Model ++ " (" ++ MB ++ ")"
+ end
+ end,
+ {ok, CPU};
+
+linux_which_cpuinfo(wind_river) ->
+ CPU =
+ case linux_cpuinfo_model() of
+ "-" ->
+ throw(noinfo);
+ Model ->
+ case linux_cpuinfo_platform() of
+ "-" ->
+ Model;
+ Platform ->
+ Model ++ " (" ++ Platform ++ ")"
+ end
+ end,
+ case linux_cpuinfo_total_bogomips() of
+ "-" ->
+ {ok, CPU};
+ BMips ->
+ {ok, {CPU, BMips}}
+ end;
+
+%% Check for x86 (Intel or AMD)
+linux_which_cpuinfo(other) ->
+ CPU =
+ case linux_cpuinfo_model_name() of
+ "-" ->
%% ARM (at least some distros...)
- try [string:trim(S) || S <- string:tokens(os:cmd("grep \"Processor\" /proc/cpuinfo"), [$:,$\n])] of
- ["Processor", Proc | _] ->
- Proc;
- _ ->
+ case linux_cpuinfo_processor() of
+ "-" ->
%% Ok, we give up
- throw(noinfo)
- catch
- _:_:_ ->
- throw(noinfo)
- end
- catch
- _:_:_ ->
- throw(noinfo)
+ throw(noinfo);
+ Proc ->
+ Proc
+ end;
+ ModelName ->
+ ModelName
end,
- try [string:trim(S) || S <- string:tokens(os:cmd("grep -i \"bogomips\" /proc/cpuinfo"), [$:,$\n])] of
- [_, BMips | _] ->
- {ok, {CPU, BMips}};
- _ ->
- {ok, CPU}
- catch
- _:_:_ ->
- {ok, CPU}
+ case linux_cpuinfo_bogomips() of
+ "-" ->
+ {ok, CPU};
+ BMips ->
+ {ok, {CPU, BMips}}
end.
+linux_meminfo_lookup(Key) when is_list(Key) ->
+ linux_info_lookup(Key, "/proc/meminfo").
+
+linux_meminfo_memtotal() ->
+ case linux_meminfo_lookup("MemTotal") of
+ [X] ->
+ X;
+ _ ->
+ "-"
+ end.
+
%% We *add* the value this return to the Factor.
linux_which_meminfo() ->
- try [string:trim(S) || S <- string:tokens(os:cmd("grep MemTotal /proc/meminfo"), [$:])] of
- [_, MemTotal] ->
+ case linux_meminfo_memtotal() of
+ "-" ->
+ 0;
+ MemTotal ->
io:format("Memory:"
"~n ~s"
"~n", [MemTotal]),
@@ -958,18 +1195,13 @@ linux_which_meminfo() ->
(MemSz3 >= 4194304) ->
1;
(MemSz3 >= 2097152) ->
- 2;
+ 3;
true ->
- 3
+ 5
end;
_X ->
0
- end;
- _ ->
- 0
- catch
- _:_:_ ->
- 0
+ end
end.
@@ -1056,12 +1288,12 @@ analyze_and_print_openbsd_host_info(Version) ->
true ->
3
end,
- CPUFactor + MemAddFactor
+ {CPUFactor + MemAddFactor, []}
end
catch
_:_:_ ->
io:format("TS Scale Factor: ~w~n", [timetrap_scale_factor()]),
- 1
+ {2, []}
end.
@@ -1144,7 +1376,7 @@ analyze_and_print_freebsd_host_info(Version) ->
true ->
3
end,
- CPUFactor + MemAddFactor
+ {CPUFactor + MemAddFactor, []}
end
catch
_:_:_ ->
@@ -1152,14 +1384,15 @@ analyze_and_print_freebsd_host_info(Version) ->
"~n Num Schedulers: ~s"
"~n", [str_num_schedulers()]),
io:format("TS Scale Factor: ~w~n", [timetrap_scale_factor()]),
- case erlang:system_info(schedulers) of
- 1 ->
- 10;
- 2 ->
- 5;
- _ ->
- 2
- end
+ Factor = case erlang:system_info(schedulers) of
+ 1 ->
+ 10;
+ 2 ->
+ 5;
+ _ ->
+ 2
+ end,
+ {Factor, []}
end.
@@ -1283,21 +1516,22 @@ analyze_and_print_netbsd_host_info(Version) ->
true ->
3
end,
- CPUFactor + MemAddFactor
+ {CPUFactor + MemAddFactor, []}
end
catch
_:_:_ ->
io:format("CPU:"
"~n Num Schedulers: ~w"
"~n", [erlang:system_info(schedulers)]),
- case erlang:system_info(schedulers) of
- 1 ->
- 10;
- 2 ->
- 5;
- _ ->
- 2
- end
+ Factor = case erlang:system_info(schedulers) of
+ 1 ->
+ 10;
+ 2 ->
+ 5;
+ _ ->
+ 2
+ end,
+ {Factor, []}
end.
analyze_netbsd_cpu(Extract) ->
@@ -1336,6 +1570,289 @@ analyze_netbsd_item(Extract, Key, Process, Default) ->
+%% Model Identifier: Macmini7,1
+%% Processor Name: Intel Core i5
+%% Processor Speed: 2,6 GHz
+%% Number of Processors: 1
+%% Total Number of Cores: 2
+%% L2 Cache (per Core): 256 KB
+%% L3 Cache: 3 MB
+%% Hyper-Threading Technology: Enabled
+%% Memory: 16 GB
+
+analyze_and_print_darwin_host_info(Version) ->
+ %% This stuff is for macOS.
+ %% If we ever tested on a pure darwin machine,
+ %% we need to find some other way to find some info...
+ %% Also, I suppose its possible that we for some other
+ %% reason *fail* to get the info...
+ case analyze_darwin_software_info() of
+ [] ->
+ io:format("Darwin:"
+ "~n Version: ~s"
+ "~n Num Schedulers: ~s"
+ "~n", [Version, str_num_schedulers()]),
+ {num_schedulers_to_factor(), []};
+ SwInfo when is_list(SwInfo) ->
+ SystemVersion = analyze_darwin_sw_system_version(SwInfo),
+ KernelVersion = analyze_darwin_sw_kernel_version(SwInfo),
+ HwInfo = analyze_darwin_hardware_info(),
+ ModelName = analyze_darwin_hw_model_name(HwInfo),
+ ModelId = analyze_darwin_hw_model_identifier(HwInfo),
+ ProcName = analyze_darwin_hw_processor_name(HwInfo),
+ ProcSpeed = analyze_darwin_hw_processor_speed(HwInfo),
+ NumProc = analyze_darwin_hw_number_of_processors(HwInfo),
+ NumCores = analyze_darwin_hw_total_number_of_cores(HwInfo),
+ Memory = analyze_darwin_hw_memory(HwInfo),
+ io:format("Darwin:"
+ "~n System Version: ~s"
+ "~n Kernel Version: ~s"
+ "~n Model: ~s (~s)"
+ "~n Processor: ~s (~s, ~s, ~s)"
+ "~n Memory: ~s"
+ "~n Num Schedulers: ~s"
+ "~n", [SystemVersion, KernelVersion,
+ ModelName, ModelId,
+ ProcName, ProcSpeed, NumProc, NumCores,
+ Memory,
+ str_num_schedulers()]),
+ CPUFactor = analyze_darwin_cpu_to_factor(ProcName,
+ ProcSpeed,
+ NumProc,
+ NumCores),
+ MemFactor = analyze_darwin_memory_to_factor(Memory),
+ if (MemFactor =:= 1) ->
+ {CPUFactor, []};
+ true ->
+ {CPUFactor + MemFactor, []}
+ end
+ end.
+
+analyze_darwin_sw_system_version(SwInfo) ->
+ proplists:get_value("system version", SwInfo, "-").
+
+analyze_darwin_sw_kernel_version(SwInfo) ->
+ proplists:get_value("kernel version", SwInfo, "-").
+
+analyze_darwin_software_info() ->
+ analyze_darwin_system_profiler("SPSoftwareDataType").
+
+analyze_darwin_hw_model_name(HwInfo) ->
+ proplists:get_value("model name", HwInfo, "-").
+
+analyze_darwin_hw_model_identifier(HwInfo) ->
+ proplists:get_value("model identifier", HwInfo, "-").
+
+analyze_darwin_hw_processor_name(HwInfo) ->
+ proplists:get_value("processor name", HwInfo, "-").
+
+analyze_darwin_hw_processor_speed(HwInfo) ->
+ proplists:get_value("processor speed", HwInfo, "-").
+
+analyze_darwin_hw_number_of_processors(HwInfo) ->
+ proplists:get_value("number of processors", HwInfo, "-").
+
+analyze_darwin_hw_total_number_of_cores(HwInfo) ->
+ proplists:get_value("total number of cores", HwInfo, "-").
+
+analyze_darwin_hw_memory(HwInfo) ->
+ proplists:get_value("memory", HwInfo, "-").
+
+analyze_darwin_hardware_info() ->
+ analyze_darwin_system_profiler("SPHardwareDataType").
+
+%% This basically has the structure: "Key: Value"
+%% But could also be (for example):
+%% "Something:" (which we ignore)
+%% "Key: Value1:Value2"
+analyze_darwin_system_profiler(DataType) ->
+ %% First, make sure the program actually exist:
+ case os:cmd("which system_profiler") of
+ [] ->
+ [];
+ _ ->
+ D0 = os:cmd("system_profiler " ++ DataType),
+ D1 = string:tokens(D0, [$\n]),
+ D2 = [string:trim(S1) || S1 <- D1],
+ D3 = [string:tokens(S2, [$:]) || S2 <- D2],
+ analyze_darwin_system_profiler2(D3)
+ end.
+
+analyze_darwin_system_profiler2(L) ->
+ analyze_darwin_system_profiler2(L, []).
+
+analyze_darwin_system_profiler2([], Acc) ->
+ [{string:to_lower(K), V} || {K, V} <- lists:reverse(Acc)];
+analyze_darwin_system_profiler2([[_]|T], Acc) ->
+ analyze_darwin_system_profiler2(T, Acc);
+analyze_darwin_system_profiler2([[H1,H2]|T], Acc) ->
+ analyze_darwin_system_profiler2(T, [{H1, string:trim(H2)}|Acc]);
+analyze_darwin_system_profiler2([[H|TH0]|T], Acc) ->
+ %% Some value parts has ':' in them, so put them together
+ TH1 = colonize(TH0),
+ analyze_darwin_system_profiler2(T, [{H, string:trim(TH1)}|Acc]).
+
+%% This is only called if the length is at least 2
+colonize([L1, L2]) ->
+ L1 ++ ":" ++ L2;
+colonize([H|T]) ->
+ H ++ ":" ++ colonize(T).
+
+
+%% The memory looks like this "<size> <unit>". Example: "2 GB"
+analyze_darwin_memory_to_factor(Mem) ->
+ case [string:to_lower(S) || S <- string:tokens(Mem, [$\ ])] of
+ [_SzStr, "tb"] ->
+ 1;
+ [SzStr, "gb"] ->
+ try list_to_integer(SzStr) of
+ Sz when Sz < 2 ->
+ 20;
+ Sz when Sz < 4 ->
+ 10;
+ Sz when Sz < 8 ->
+ 5;
+ Sz when Sz < 16 ->
+ 2;
+ _ ->
+ 1
+ catch
+ _:_:_ ->
+ 20
+ end;
+ [_SzStr, "mb"] ->
+ 20;
+ _ ->
+ 20
+ end.
+
+
+%% The speed is a string: "<speed> <unit>"
+%% the speed may be a float, which we transforms into an integer of MHz.
+%% To calculate a factor based on processor speed, number of procs
+%% and number of cores is ... not an exact ... science ...
+analyze_darwin_cpu_to_factor(_ProcName,
+ ProcSpeedStr, NumProcStr, NumCoresStr) ->
+ Speed =
+ case [string:to_lower(S) || S <- string:tokens(ProcSpeedStr, [$\ ])] of
+ [SpeedStr, "mhz"] ->
+ try list_to_integer(SpeedStr) of
+ SpeedI ->
+ SpeedI
+ catch
+ _:_:_ ->
+ try list_to_float(SpeedStr) of
+ SpeedF ->
+ trunc(SpeedF)
+ catch
+ _:_:_ ->
+ -1
+ end
+ end;
+ [SpeedStr, "ghz"] ->
+ try list_to_float(SpeedStr) of
+ SpeedF ->
+ trunc(1000*SpeedF)
+ catch
+ _:_:_ ->
+ try list_to_integer(SpeedStr) of
+ SpeedI ->
+ 1000*SpeedI
+ catch
+ _:_:_ ->
+ -1
+ end
+ end;
+ _ ->
+ -1
+ end,
+ NumProc = try list_to_integer(NumProcStr) of
+ NumProcI ->
+ NumProcI
+ catch
+ _:_:_ ->
+ 1
+ end,
+ NumCores = try list_to_integer(NumCoresStr) of
+ NumCoresI ->
+ NumCoresI
+ catch
+ _:_:_ ->
+ 1
+ end,
+ if
+ (Speed > 3000) ->
+ if
+ (NumProc =:= 1) ->
+ if
+ (NumCores < 2) ->
+ 5;
+ (NumCores < 4) ->
+ 3;
+ (NumCores < 6) ->
+ 2;
+ true ->
+ 1
+ end;
+ true ->
+ if
+ (NumCores < 4) ->
+ 2;
+ true ->
+ 1
+ end
+ end;
+ (Speed > 2000) ->
+ if
+ (NumProc =:= 1) ->
+ if
+ (NumCores < 2) ->
+ 8;
+ (NumCores < 4) ->
+ 5;
+ (NumCores < 6) ->
+ 3;
+ true ->
+ 1
+ end;
+ true ->
+ if
+ (NumCores < 4) ->
+ 5;
+ (NumCores < 8) ->
+ 2;
+ true ->
+ 1
+ end
+ end;
+ true ->
+ if
+ (NumProc =:= 1) ->
+ if
+ (NumCores < 2) ->
+ 10;
+ (NumCores < 4) ->
+ 7;
+ (NumCores < 6) ->
+ 5;
+ (NumCores < 8) ->
+ 3;
+ true ->
+ 1
+ end;
+ true ->
+ if
+ (NumCores < 4) ->
+ 8;
+ (NumCores < 8) ->
+ 4;
+ true ->
+ 1
+ end
+ end
+ end.
+
+
analyze_and_print_solaris_host_info(Version) ->
Release =
case file:read_file_info("/etc/release") of
@@ -1461,19 +1978,19 @@ analyze_and_print_solaris_host_info(Version) ->
_:_:_ ->
10
end,
- try erlang:system_info(schedulers) of
- 1 ->
- 10;
- 2 ->
- 5;
- N when (N =< 6) ->
- 2;
- _ ->
- 1
- catch
- _:_:_ ->
- 10
- end + MemFactor.
+ {try erlang:system_info(schedulers) of
+ 1 ->
+ 10;
+ 2 ->
+ 5;
+ N when (N =< 6) ->
+ 2;
+ _ ->
+ 1
+ catch
+ _:_:_ ->
+ 10
+ end + MemFactor, []}.
@@ -1547,7 +2064,7 @@ analyze_and_print_win_host_info(Version) ->
_ ->
2
end,
- CPUFactor + MemFactor.
+ {CPUFactor + MemFactor, SysInfo}.
win_sys_info_lookup(Key, SysInfo) ->
win_sys_info_lookup(Key, SysInfo, "-").
@@ -1562,14 +2079,24 @@ win_sys_info_lookup(Key, SysInfo, Def) ->
%% This function only extracts the prop we actually care about!
which_win_system_info() ->
- SysInfo = os:cmd("systeminfo"),
- try process_win_system_info(string:tokens(SysInfo, [$\r, $\n]), [])
- catch
- _:_:_ ->
- io:format("Failed process System info: "
- "~s~n", [SysInfo]),
- []
- end.
+ F = fun() ->
+ try
+ begin
+ SysInfo = os:cmd("systeminfo"),
+ process_win_system_info(
+ string:tokens(SysInfo, [$\r, $\n]), [])
+ end
+ catch
+ C:E:S ->
+ io:format("Failed get or process System info: "
+ " Error Class: ~p"
+ " Error: ~p"
+ " Stack: ~p"
+ "~n", [C, E, S]),
+ []
+ end
+ end,
+ proxy_call(F, minutes(1), []).
process_win_system_info([], Acc) ->
Acc;
@@ -1613,7 +2140,39 @@ str_num_schedulers() ->
_:_:_ -> "-"
end.
-
+num_schedulers_to_factor() ->
+ try erlang:system_info(schedulers) of
+ 1 ->
+ 10;
+ 2 ->
+ 5;
+ N when (N =< 6) ->
+ 2;
+ _ ->
+ 1
+ catch
+ _:_:_ ->
+ 10
+ end.
+
+
+linux_info_lookup(Key, File) ->
+ try [string:trim(S) || S <- string:tokens(os:cmd("grep " ++ "\"" ++ Key ++ "\"" ++ " " ++ File), [$:,$\n])] of
+ Info ->
+ linux_info_lookup_collect(Key, Info, [])
+ catch
+ _:_:_ ->
+ "-"
+ end.
+
+linux_info_lookup_collect(_Key, [], Values) ->
+ lists:reverse(Values);
+linux_info_lookup_collect(Key, [Key, Value|Rest], Values) ->
+ linux_info_lookup_collect(Key, Rest, [Value|Values]);
+linux_info_lookup_collect(_, _, Values) ->
+ lists:reverse(Values).
+
+
%% ----------------------------------------------------------------
%% Time related function
@@ -1639,7 +2198,7 @@ sleep(MSecs) ->
%% ----------------------------------------------------------------
%% Process utility function
-%%
+%%
flush_mqueue() ->
io:format("~p~n", [lists:reverse(flush_mqueue([]))]).
@@ -1654,10 +2213,10 @@ flush_mqueue(MQ) ->
trap_exit() ->
- {trap_exit,Flag} = process_info(self(),trap_exit),Flag.
+ {trap_exit, Flag} = process_info(self(),trap_exit), Flag.
trap_exit(Flag) ->
- process_flag(trap_exit,Flag).
+ process_flag(trap_exit, Flag).
diff --git a/lib/snmp/test/snmp_test_lib.hrl b/lib/snmp/test/snmp_test_lib.hrl
index f4863c9a1e..a853d3cc09 100644
--- a/lib/snmp/test/snmp_test_lib.hrl
+++ b/lib/snmp/test/snmp_test_lib.hrl
@@ -52,10 +52,13 @@
-define(OS_BASED_SKIP(Skippable), ?LIB:os_based_skip(Skippable)).
-define(NON_PC_TC_MAYBE_SKIP(Config, Condition),
?LIB:non_pc_tc_maybe_skip(Config, Condition, ?MODULE, ?LINE)).
+
-define(SKIP(Reason), ?LIB:skip(Reason, ?MODULE, ?LINE)).
-define(FAIL(Reason), ?LIB:fail(Reason, ?MODULE, ?LINE)).
-define(HAS_SUPPORT_IPV6(), ?LIB:has_support_ipv6()).
+-define(PCALL(F, T, D), ?LIB:proxy_call(F, T, D)).
+
%% - Time macros -
diff --git a/lib/snmp/test/snmp_test_manager.erl b/lib/snmp/test/snmp_test_manager.erl
index 6ab5ce164c..fcbc0dff6b 100644
--- a/lib/snmp/test/snmp_test_manager.erl
+++ b/lib/snmp/test/snmp_test_manager.erl
@@ -193,22 +193,22 @@ handle_call(stop, _From, S) ->
handle_call({sync_get, Oids}, _From,
#state{agent_target_name = TargetName} = S) ->
- Reply = (catch snmpm:sync_get(?USER, TargetName, Oids)),
+ Reply = (catch snmpm:sync_get2(?USER, TargetName, Oids)),
{reply, Reply, S};
handle_call({sync_get_next, Oids}, _From,
#state{agent_target_name = TargetName} = S) ->
- Reply = (catch snmpm:sync_get_next(?USER, TargetName, Oids)),
+ Reply = (catch snmpm:sync_get_next2(?USER, TargetName, Oids)),
{reply, Reply, S};
handle_call({sync_get_bulk, NR, MR, Oids}, _From,
#state{agent_target_name = TargetName} = S) ->
- Reply = (catch snmpm:sync_get_bulk(?USER, TargetName, NR, MR, Oids)),
+ Reply = (catch snmpm:sync_get_bulk2(?USER, TargetName, NR, MR, Oids)),
{reply, Reply, S};
handle_call({sync_set, VarsAndVals}, _From,
#state{agent_target_name = TargetName} = S) ->
- Reply = (catch snmpm:sync_set(?USER, TargetName, VarsAndVals)),
+ Reply = (catch snmpm:sync_set2(?USER, TargetName, VarsAndVals)),
{reply, Reply, S};
handle_call(Req, From, State) ->
diff --git a/lib/snmp/test/snmp_to_snmpnet_SUITE.erl b/lib/snmp/test/snmp_to_snmpnet_SUITE.erl
index b5c08fb697..aacdcff504 100644
--- a/lib/snmp/test/snmp_to_snmpnet_SUITE.erl
+++ b/lib/snmp/test/snmp_to_snmpnet_SUITE.erl
@@ -435,9 +435,9 @@ erlang_manager_netsnmp_get(Config) when is_list(Config) ->
{version, v2}, {sec_model, v2c}, {sec_level, noAuthNoPriv}])
|| {Domain, Addr} <- Transports],
Results =
- [snmp_manager_user:sync_get(
+ [snmp_manager_user:sync_get2(
TargetName++domain_suffix(Domain),
- [?sysDescr_instance])
+ [?sysDescr_instance], [])
|| {Domain, _} <- Transports],
ct:pal("sync_get -> ~p", [Results]),
snmp_manager_user:stop(),
diff --git a/lib/snmp/vsn.mk b/lib/snmp/vsn.mk
index 84299ec250..39da86b2e1 100644
--- a/lib/snmp/vsn.mk
+++ b/lib/snmp/vsn.mk
@@ -19,6 +19,6 @@
# %CopyrightEnd%
APPLICATION = snmp
-SNMP_VSN = 5.5
+SNMP_VSN = 5.6
PRE_VSN =
APP_VSN = "$(APPLICATION)-$(SNMP_VSN)$(PRE_VSN)"
diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml
index 4492b937a2..1cc25c00b3 100644
--- a/lib/ssh/doc/src/notes.xml
+++ b/lib/ssh/doc/src/notes.xml
@@ -30,6 +30,247 @@
<file>notes.xml</file>
</header>
+<section><title>Ssh 4.10</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix error in ssh_sftpd typespec.</p>
+ <p>
+ Own Id: OTP-16363</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ The plug-in file ssh_file.erl, that is responsible for
+ default file handling, is re-factored, optimized and
+ re-written.</p>
+ <p>
+ Own Id: OTP-11688 Aux Id: OTP-12699 </p>
+ </item>
+ <item>
+ <p>
+ OpenSSH 6.5 introduced a new file representation of keys
+ called <url
+ href="https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.key?annotate=1.1">openssh-key-v1</url>.</p>
+ <p>
+ OTP/SSH had an experimental implementation of this
+ format. That implementation is now improved and supported
+ with the exception of handling encrypted keys.</p>
+ <p>
+ Own Id: OTP-15434</p>
+ </item>
+ <item>
+ <p>
+ TCP/IP port forwarding, a.k.a tunneling a.k.a
+ tcp-forward/direct-tcp is implemented. In the OpenSSH
+ client, this corresponds to the options -L and -R.</p>
+ <p>
+ The client or server listens to a specified socket, and
+ when something connects to it with TCP/IP, that
+ connection is forwarded in an encrypted tunnel to the
+ peer. The peer then connects to a predefined IP/port pair
+ and then acts as a proxy.</p>
+ <p>
+ See the manual, <seemfa
+ marker="ssh:ssh#tcpip_tunnel_to_server/6"><c>ssh:tcpip_tunnel_to_server/6</c></seemfa>
+ and <seemfa
+ marker="ssh:ssh#tcpip_tunnel_from_server/6"><c>ssh:tcpip_tunnel_from_server/6</c></seemfa>.</p>
+ <p>
+ The functionality is disabled per default but can be
+ enabled when starting a daemon.</p>
+ <p>
+ Own Id: OTP-15998 Aux Id: PR-2376, PR-2368 </p>
+ </item>
+ <item>
+ <p>
+ The client-side of the supervisor tree (under sshc_sup)
+ was previously not complete; the channel handling
+ processes were handled with links but had no supervisors.</p>
+ <p>
+ This is now corrected with a client-side supervisor tree
+ under <c>sshc_sup</c>, similar to the server-side
+ supervisor tree under <c>sshd_sup</c>.</p>
+ <p>
+ Own Id: OTP-16026 Aux Id: PR-2368, (OTP-15998) </p>
+ </item>
+ <item>
+ <p>
+ The extension <url
+ href="https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL?annotate=HEAD">posix-rename@openssh.com</url>
+ is added to the <seemfa
+ marker="ssh:ssh_sftp#rename/3">ssh/sftp rename</seemfa>
+ operation.</p>
+ <p>
+ Own Id: OTP-16289 Aux Id: PR-2448 </p>
+ </item>
+ <item>
+ <p>
+ Calls of deprecated functions in the <seeguide
+ marker="crypto:new_api#the-old-api">Old Crypto
+ API</seeguide> are replaced by calls of their <seeguide
+ marker="crypto:new_api#the-new-api">substitutions</seeguide>.</p>
+ <p>
+ Own Id: OTP-16346</p>
+ </item>
+ <item>
+ <p>
+ The default known_hosts file handling is improved to
+ include ports.</p>
+ <p>
+ The handling of the contents in that file is updated to
+ support the <url
+ href="https://man.openbsd.org/sshd#SSH_KNOWN_HOSTS_FILE_FORMAT">full
+ syntax</url>, with exception of 1) the wildcard '?', 2)
+ wildcards in canonical names and 3) the option
+ '@cert-authority'</p>
+ <p>
+ Own Id: OTP-16506</p>
+ </item>
+ <item>
+ <p>
+ The MAC (Message Authorization Code) algorithms</p>
+ <list> <item>hmac-sha1-etm@openssh.com</item>
+ <item>hmac-sha2-256-etm@openssh.com</item>
+ <item>hmac-sha2-512-etm@openssh.com</item> </list> <p>are
+ implemented.</p>
+ <p>
+ Own Id: OTP-16508</p>
+ </item>
+ <item>
+ <p>
+ The key-exchange algorithms
+ <c>'diffie-hellman-group14-sha1'</c> and
+ <c>'diffie-hellman-group-exchange-sha1'</c> are disabled
+ per default. The reason is that SHA1 now is considered
+ insecure.</p>
+ <p>
+ They can be enabled if needed, see <seeapp
+ marker="ssh:SSH_app#algorithms">SSH (App)</seeapp>.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16509</p>
+ </item>
+ <item>
+ <p>
+ The public key algorithm <c>'ssh-dss'</c> is disabled per
+ default. The reason is that it is now considered as
+ insecure.</p>
+ <p>
+ It can be enabled if needed, see <seeapp
+ marker="ssh:SSH_app#algorithms">SSH (App)</seeapp>.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16510</p>
+ </item>
+ <item>
+ <p>
+ The public key <c>'ssh-rsa'</c> is now considered as
+ insecure because of its usage of SHA1.</p>
+ <p>
+ It is therefore deprecated and will no longer be enabled
+ per default in OTP-24.0.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16511</p>
+ </item>
+ <item>
+ <p>
+ An option <seetype
+ marker="ssh:ssh_file#optimize_key_lookup">optimize
+ (optimize_key_lookup)</seetype> is introduced for the
+ file interface ssh_file.erl</p>
+ <p>
+ The option enables the user to select between the default
+ handling which is fast but memory consuming vs memory
+ efficient but not as fast. The effect might be observable
+ only for large files.</p>
+ <p>
+ See the manual for <seemfa
+ marker="ssh:ssh_file#is_host_key/5">ssh_file:is_host_key/5</seemfa>
+ and <seemfa
+ marker="ssh:ssh_file#is_auth_key/3">ssh_file:is_auth_key/3</seemfa>.</p>
+ <p>
+ Own Id: OTP-16512</p>
+ </item>
+ <item>
+ <p>
+ The ssh agent is now implemented in the ssh_agent key
+ callback module. </p>
+ <p>
+ Enable with the the option <c> {key_cb, {ssh_agent,
+ []}}</c> in for example ssh:connect/3.</p>
+ <p>
+ See the <seeerl marker="ssh:ssh_agent">ssh_agent
+ manual</seeerl> for details.</p>
+ <p>
+ Own Id: OTP-16513</p>
+ </item>
+ <item>
+ <p>
+ Algorithm configuration could now be done in a .config
+ file.</p>
+ <p>
+ This is useful for example to enable an algorithm that is
+ disabled by default. It could now be enabled in an
+ .config-file without changing the code,</p>
+ <p>
+ See the SSH User's Guide chapter <seeguide
+ marker="ssh:configurations">"Configuration in
+ SSH"</seeguide>.</p>
+ <p>
+ Own Id: OTP-16540</p>
+ </item>
+ <item>
+ <p>
+ Documented which gen_tcp socket options can't be used in
+ calls to ssh:connect and ssh:daemon.</p>
+ <p>
+ Own Id: OTP-16589</p>
+ </item>
+ <item>
+ <p>
+ Added <seetype
+ marker="ssh:ssh#kb_int_fun_4">kb_int_fun_4()</seetype> to
+ the <seetype
+ marker="ssh:ssh#authentication_daemon_options">authentication_daemon_options()</seetype>
+ to enable generating dynamic keyboard-interactive prompts
+ from the user's state returned from the authentication
+ fun <seetype
+ marker="ssh:ssh#pwdfun_4">pwdfun_4()</seetype>.</p>
+ <p>
+ Own Id: OTP-16622 Aux Id: PR-2604 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.9.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Potential hazard between re-keying decision and socket
+ close.</p>
+ <p>
+ Own Id: OTP-16462 Aux Id: ERIERL-464 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Ssh 4.9</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -303,6 +544,22 @@
</section>
+<section><title>Ssh 4.7.6.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Potential hazard between re-keying decision and socket
+ close.</p>
+ <p>
+ Own Id: OTP-16462 Aux Id: ERIERL-464 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Ssh 4.7.6.3</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 ca6a02117b..f7818b809a 100644
--- a/lib/ssh/doc/src/ssh.xml
+++ b/lib/ssh/doc/src/ssh.xml
@@ -179,6 +179,17 @@
<p>Options for <seemfa marker="#connect/3">clients</seemfa>.
The individual options are further explained below or by following the hyperlinks.
</p>
+ <p>Note that not every
+ <seetype marker="kernel:gen_tcp#connect_option">gen_tcp:connect_option()</seetype>
+ is accepted. See
+ <seemfa marker="ssh#set_sock_opts/2">set_sock_opts/2</seemfa>
+ for a list of prohibited options.
+ </p>
+ <p>Also note that setting a
+ <seetype marker="kernel:gen_tcp#connect_option">gen_tcp:connect_option()</seetype>
+ could change the socket in a way that impacts the ssh client's behaviour
+ negatively. You use it on your own risk.
+ </p>
</desc>
</datatype>
@@ -341,6 +352,17 @@
<p>Options for <seemfa marker="#daemon/1">daemons</seemfa>.
The individual options are further explained below or by following the hyperlinks.
</p>
+ <p>Note that not every
+ <seetype marker="kernel:gen_tcp#listen_option">gen_tcp:listen_option()</seetype>
+ is accepted. See
+ <seemfa marker="ssh#set_sock_opts/2">set_sock_opts/2</seemfa>
+ for a list of prohibited options.
+ </p>
+ <p>Also note that setting a
+ <seetype marker="kernel:gen_tcp#listen_option">gen_tcp:listen_option()</seetype>
+ could change the socket in a way that impacts the ssh deamon's behaviour
+ negatively. You use it on your own risk.
+ </p>
</desc>
</datatype>
@@ -514,6 +536,7 @@
<name name="prompt_texts"/>
<name name="kb_int_tuple"/>
<name name="kb_int_fun_3"/>
+ <name name="kb_int_fun_4"/>
<name name="pwdfun_2"/>
<name name="pwdfun_4"/>
<desc>
@@ -522,7 +545,7 @@
<item>
<p>Sets the text strings that the daemon sends to the client for presentation to the user when
using <c>keyboard-interactive</c> authentication.</p>
- <p>If the fun/3 is used, it is called when the actual authentication occurs and may therefore
+ <p>If the fun/3 or fun/4 is used, it is called when the actual authentication occurs and may therefore
return dynamic data like time, remote ip etc.</p>
<p>The parameter <c>Echo</c> guides the client about need to hide the password.</p>
<p>The default value is:
@@ -874,7 +897,7 @@
<datatype>
<name name="disconnectfun_common_option"/>
<desc>
- <p>Provides a fun to implement your own logging when the peer disconnects.</p>
+ <p>Provides a fun to implement your own logging or other handling at disconnects.</p>
</desc>
</datatype>
@@ -1221,8 +1244,17 @@
<p>This function calls the
<seemfa marker="kernel:inet#setopts/2">inet:setopts/2</seemfa>, read that documentation and
for <seetype marker="kernel:gen_tcp#option">gen_tcp:option()</seetype>.
- All gen_tcp socket options except <c>active</c>, <c>deliver</c>, <c>mode</c> and <c>packet</c>
- are allowed. The excluded options are reserved by the SSH application.
+ </p>
+ <p>
+ All gen_tcp socket options except
+ </p>
+ <list>
+ <item><c>active</c></item>
+ <item><c>deliver</c></item>
+ <item><c>mode</c> and</item>
+ <item><c>packet</c></item>
+ </list>
+ <p>are allowed. The excluded options are reserved by the SSH application.
</p>
<warning>
<p>This is an extremly dangerous function. You use it on your own risk.</p>
diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl
index ffa2ec8c06..ad90a1a841 100644
--- a/lib/ssh/src/ssh.hrl
+++ b/lib/ssh/src/ssh.hrl
@@ -361,9 +361,11 @@
-type prompt_texts() ::
kb_int_tuple()
| kb_int_fun_3()
+ | kb_int_fun_4()
.
-type kb_int_fun_3() :: fun((Peer::ip_port(), User::string(), Service::string()) -> kb_int_tuple()).
+-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()) .
diff --git a/lib/ssh/src/ssh_acceptor.erl b/lib/ssh/src/ssh_acceptor.erl
index 15e59dd1fe..960058fa38 100644
--- a/lib/ssh/src/ssh_acceptor.erl
+++ b/lib/ssh/src/ssh_acceptor.erl
@@ -217,6 +217,11 @@ ssh_dbg_format(connections, {call, {?MODULE,acceptor_init,
[_Parent, Port, Address, _Opts, _AcceptTimeout]}}) ->
[io_lib:format("Starting LISTENER on ~s:~p\n", [ntoa(Address),Port])
];
+ssh_dbg_format(connections, {return_from, {?MODULE,acceptor_init,5}, _Ret}) ->
+ skip;
+
+ssh_dbg_format(connections, {call, {?MODULE,handle_connection,[_,_,_,_,_]}}) ->
+ skip;
ssh_dbg_format(connections, {return_from, {?MODULE,handle_connection,5}, {error,Error}}) ->
["Starting connection to server failed:\n",
io_lib:format("Error = ~p", [Error])
diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl
index a42f034f1b..19df20c9f1 100644
--- a/lib/ssh/src/ssh_auth.erl
+++ b/lib/ssh/src/ssh_auth.erl
@@ -32,10 +32,13 @@
-export([get_public_key/2,
publickey_msg/1, password_msg/1, keyboard_interactive_msg/1,
service_request_msg/1, init_userauth_request_msg/1,
- userauth_request_msg/1, handle_userauth_request/3,
+ userauth_request_msg/1, handle_userauth_request/3, ssh_msg_userauth_result/1,
handle_userauth_info_request/2, handle_userauth_info_response/2
]).
+-behaviour(ssh_dbg).
+-export([ssh_dbg_trace_points/0, ssh_dbg_flags/1, ssh_dbg_on/1, ssh_dbg_off/1, ssh_dbg_format/3]).
+
%%--------------------------------------------------------------------
%%% Internal application API
%%--------------------------------------------------------------------
@@ -110,14 +113,13 @@ password_msg([#ssh{opts = Opts,
not_ok ->
{not_ok, Ssh};
_ ->
- ssh_transport:ssh_packet(
- #ssh_msg_userauth_request{user = User,
- service = Service,
- method = "password",
- data =
- <<?BOOLEAN(?FALSE),
- ?STRING(unicode:characters_to_binary(Password))>>},
- Ssh)
+ {#ssh_msg_userauth_request{user = User,
+ service = Service,
+ method = "password",
+ data =
+ <<?BOOLEAN(?FALSE),
+ ?STRING(unicode:characters_to_binary(Password))>>},
+ Ssh}
end.
%% See RFC 4256 for info on keyboard-interactive
@@ -128,13 +130,12 @@ keyboard_interactive_msg([#ssh{user = User,
not_ok ->
{not_ok,Ssh}; % No need to use a failed pwd once more
_ ->
- ssh_transport:ssh_packet(
- #ssh_msg_userauth_request{user = User,
- service = Service,
- method = "keyboard-interactive",
- data = << ?STRING(<<"">>),
- ?STRING(<<>>) >> },
- Ssh)
+ {#ssh_msg_userauth_request{user = User,
+ service = Service,
+ method = "keyboard-interactive",
+ data = << ?STRING(<<"">>),
+ ?STRING(<<>>) >> },
+ Ssh}
end.
@@ -183,15 +184,14 @@ publickey_msg([SigAlg, #ssh{user = User,
SigBlob = list_to_binary([?string(SigAlgStr),
?binary(Sig)]),
- ssh_transport:ssh_packet(
- #ssh_msg_userauth_request{user = User,
- service = Service,
- method = "publickey",
- data = [?TRUE,
- ?string(SigAlgStr),
- ?binary(PubKeyBlob),
- ?binary(SigBlob)]},
- Ssh);
+ {#ssh_msg_userauth_request{user = User,
+ service = Service,
+ method = "publickey",
+ data = [?TRUE,
+ ?string(SigAlgStr),
+ ?binary(PubKeyBlob),
+ ?binary(SigBlob)]},
+ Ssh};
_ ->
{not_ok, Ssh}
@@ -199,8 +199,8 @@ publickey_msg([SigAlg, #ssh{user = User,
%%%----------------------------------------------------------------
service_request_msg(Ssh) ->
- ssh_transport:ssh_packet(#ssh_msg_service_request{name = "ssh-userauth"},
- Ssh#ssh{service = "ssh-userauth"}).
+ {#ssh_msg_service_request{name = "ssh-userauth"},
+ Ssh#ssh{service = "ssh-userauth"}}.
%%%----------------------------------------------------------------
init_userauth_request_msg(#ssh{opts = Opts} = Ssh) ->
@@ -210,24 +210,23 @@ init_userauth_request_msg(#ssh{opts = Opts} = Ssh) ->
?DISCONNECT(?SSH_DISCONNECT_ILLEGAL_USER_NAME,
"Could not determine the users name");
User ->
- ssh_transport:ssh_packet(
- #ssh_msg_userauth_request{user = User,
- service = "ssh-connection",
- method = "none",
- data = <<>>},
- Ssh#ssh{user = User,
- userauth_preference = method_preference(Ssh#ssh.userauth_pubkeys),
- userauth_methods = none,
- service = "ssh-connection"}
- )
+ {#ssh_msg_userauth_request{user = User,
+ service = "ssh-connection",
+ method = "none",
+ data = <<>>},
+ Ssh#ssh{user = User,
+ userauth_preference = method_preference(Ssh#ssh.userauth_pubkeys),
+ userauth_methods = none,
+ service = "ssh-connection"}
+ }
end.
%%%----------------------------------------------------------------
%%% called by server
handle_userauth_request(#ssh_msg_service_request{name = Name = "ssh-userauth"},
_, Ssh) ->
- {ok, ssh_transport:ssh_packet(#ssh_msg_service_accept{name = Name},
- Ssh#ssh{service = "ssh-connection"})};
+ {ok, {#ssh_msg_service_accept{name = Name},
+ Ssh#ssh{service = "ssh-connection"}}};
handle_userauth_request(#ssh_msg_userauth_request{user = User,
service = "ssh-connection",
@@ -239,12 +238,13 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
case check_password(User, Password, Opts, Ssh) of
{true,Ssh1} ->
{authorized, User,
- ssh_transport:ssh_packet(#ssh_msg_userauth_success{}, Ssh1)};
+ {#ssh_msg_userauth_success{}, Ssh1}
+ };
{false,Ssh1} ->
{not_authorized, {User, {error,"Bad user or password"}},
- ssh_transport:ssh_packet(#ssh_msg_userauth_failure{
- authentications = Methods,
- partial_success = false}, Ssh1)}
+ {#ssh_msg_userauth_failure{authentications = Methods,
+ partial_success = false}, Ssh1}
+ }
end;
handle_userauth_request(#ssh_msg_userauth_request{user = User,
@@ -264,18 +264,18 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
%% or the old password was bad.
{not_authorized, {User, {error,"Password change not supported"}},
- ssh_transport:ssh_packet(#ssh_msg_userauth_failure{
- authentications = Methods,
- partial_success = false}, Ssh)};
+ {#ssh_msg_userauth_failure{authentications = Methods,
+ partial_success = false}, Ssh}
+ };
handle_userauth_request(#ssh_msg_userauth_request{user = User,
service = "ssh-connection",
method = "none"}, _,
#ssh{userauth_supported_methods = Methods} = Ssh) ->
{not_authorized, {User, undefined},
- ssh_transport:ssh_packet(
- #ssh_msg_userauth_failure{authentications = Methods,
- partial_success = false}, Ssh)};
+ {#ssh_msg_userauth_failure{authentications = Methods,
+ partial_success = false}, Ssh}
+ };
handle_userauth_request(#ssh_msg_userauth_request{user = User,
service = "ssh-connection",
@@ -293,14 +293,14 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
case pre_verify_sig(User, KeyBlob, Opts) of
true ->
{not_authorized, {User, undefined},
- ssh_transport:ssh_packet(
- #ssh_msg_userauth_pk_ok{algorithm_name = binary_to_list(BAlg),
- key_blob = KeyBlob}, Ssh)};
+ {#ssh_msg_userauth_pk_ok{algorithm_name = binary_to_list(BAlg),
+ key_blob = KeyBlob}, Ssh}
+ };
false ->
{not_authorized, {User, undefined},
- ssh_transport:ssh_packet(#ssh_msg_userauth_failure{
- authentications = Methods,
- partial_success = false}, Ssh)}
+ {#ssh_msg_userauth_failure{authentications = Methods,
+ partial_success = false}, Ssh}
+ }
end;
handle_userauth_request(#ssh_msg_userauth_request{user = User,
@@ -318,13 +318,13 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
BAlg, KeyBlob, SigWLen, Ssh) of
true ->
{authorized, User,
- ssh_transport:ssh_packet(
- #ssh_msg_userauth_success{}, Ssh)};
+ {#ssh_msg_userauth_success{}, Ssh}
+ };
false ->
{not_authorized, {User, undefined},
- ssh_transport:ssh_packet(#ssh_msg_userauth_failure{
- authentications = Methods,
- partial_success = false}, Ssh)}
+ {#ssh_msg_userauth_failure{authentications = Methods,
+ partial_success = false}, Ssh}
+ }
end;
handle_userauth_request(#ssh_msg_userauth_request{user = User,
@@ -337,9 +337,9 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
case KbTriesLeft of
N when N<1 ->
{not_authorized, {User, {authmethod, "keyboard-interactive"}},
- ssh_transport:ssh_packet(
- #ssh_msg_userauth_failure{authentications = Methods,
- partial_success = false}, Ssh)};
+ {#ssh_msg_userauth_failure{authentications = Methods,
+ partial_success = false}, Ssh}
+ };
_ ->
%% RFC4256
@@ -364,6 +364,9 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
Default;
{_,_,_,_}=V ->
V;
+ F when is_function(F, 4) ->
+ {_,PeerName} = Ssh#ssh.peer,
+ F(PeerName, User, "ssh-connection", Ssh#ssh.pwdfun_user_state);
F when is_function(F) ->
{_,PeerName} = Ssh#ssh.peer,
F(PeerName, User, "ssh-connection")
@@ -381,8 +384,8 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
>>
},
{not_authorized, {User, undefined},
- ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User
- })}
+ {Msg, Ssh#ssh{user = User}}
+ }
end;
handle_userauth_request(#ssh_msg_userauth_request{user = User,
@@ -390,9 +393,9 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
method = Other}, _,
#ssh{userauth_supported_methods = Methods} = Ssh) ->
{not_authorized, {User, {authmethod, Other}},
- ssh_transport:ssh_packet(
- #ssh_msg_userauth_failure{authentications = Methods,
- partial_success = false}, Ssh)}.
+ {#ssh_msg_userauth_failure{authentications = Methods,
+ partial_success = false}, Ssh}
+ }.
%%%----------------------------------------------------------------
@@ -408,9 +411,9 @@ handle_userauth_info_request(#ssh_msg_userauth_info_request{name = Name,
not_ok;
Responses ->
{ok,
- ssh_transport:ssh_packet(
- #ssh_msg_userauth_info_response{num_responses = NumPrompts,
- data = Responses}, Ssh)}
+ {#ssh_msg_userauth_info_response{num_responses = NumPrompts,
+ data = Responses},
+ Ssh}}
end.
%%%----------------------------------------------------------------
@@ -428,32 +431,30 @@ handle_userauth_info_response(#ssh_msg_userauth_info_response{num_responses = 1,
case check_password(User, unicode:characters_to_list(Password), Opts, Ssh) of
{true,Ssh1} when SendOneEmpty==true ->
- Msg = #ssh_msg_userauth_info_request{name = "",
- instruction = "",
- language_tag = "",
- num_prompts = 0,
- data = <<?BOOLEAN(?FALSE)>>
- },
{authorized_but_one_more, User,
- ssh_transport:ssh_packet(Msg, Ssh1)};
+ {#ssh_msg_userauth_info_request{name = "",
+ instruction = "",
+ language_tag = "",
+ num_prompts = 0,
+ data = <<?BOOLEAN(?FALSE)>>
+ },
+ Ssh1}};
{true,Ssh1} ->
{authorized, User,
- ssh_transport:ssh_packet(#ssh_msg_userauth_success{}, Ssh1)};
+ {#ssh_msg_userauth_success{}, Ssh1}};
{false,Ssh1} ->
{not_authorized, {User, {error,"Bad user or password"}},
- ssh_transport:ssh_packet(#ssh_msg_userauth_failure{
- authentications = Methods,
- partial_success = false},
- Ssh1#ssh{kb_tries_left = max(KbTriesLeft-1, 0)}
- )}
+ {#ssh_msg_userauth_failure{authentications = Methods,
+ partial_success = false},
+ Ssh1#ssh{kb_tries_left = max(KbTriesLeft-1, 0)}}}
end;
handle_userauth_info_response({extra,#ssh_msg_userauth_info_response{}},
#ssh{user = User} = Ssh) ->
{authorized, User,
- ssh_transport:ssh_packet(#ssh_msg_userauth_success{}, Ssh)};
+ {#ssh_msg_userauth_success{}, Ssh}};
handle_userauth_info_response(#ssh_msg_userauth_info_response{},
_Auth) ->
@@ -619,3 +620,193 @@ write_if_nonempty(_, "") -> ok;
write_if_nonempty(_, <<>>) -> ok;
write_if_nonempty(IoCb, Text) -> IoCb:format("~s~n",[Text]).
+%%%----------------------------------------------------------------
+%%% Called just for the tracer ssh_dbg
+ssh_msg_userauth_result(_R) -> ok.
+
+%%%################################################################
+%%%#
+%%%# Tracing
+%%%#
+
+ssh_dbg_trace_points() -> [authentication].
+
+ssh_dbg_flags(authentication) -> [c].
+
+ssh_dbg_on(authentication) -> dbg:tp(?MODULE, handle_userauth_request, 3, x),
+ dbg:tp(?MODULE, init_userauth_request_msg, 1, x),
+ dbg:tp(?MODULE, ssh_msg_userauth_result, 1, x),
+ dbg:tp(?MODULE, userauth_request_msg, 1, x).
+
+ssh_dbg_off(authentication) -> dbg:ctpg(?MODULE, handle_userauth_request, 3),
+ dbg:ctpg(?MODULE, init_userauth_request_msg, 1),
+ dbg:ctpg(?MODULE, ssh_msg_userauth_result, 1),
+ dbg:ctpg(?MODULE, userauth_request_msg, 1).
+
+
+
+%%% Server ----------------
+ssh_dbg_format(authentication, {call, {?MODULE,handle_userauth_request, [Req,_SessionID,Ssh]}},
+ Stack) ->
+ {skip, [{Req,Ssh}|Stack]};
+
+
+ssh_dbg_format(authentication, {return_from, {?MODULE,handle_userauth_request,3},
+ {ok,{#ssh_msg_service_accept{name=Name},_Ssh}}},
+ [{#ssh_msg_service_request{name=Name},_} | Stack]) ->
+ {skip, Stack};
+
+ssh_dbg_format(authentication, {return_from, {?MODULE,handle_userauth_request,3},
+ {authorized,User,_Repl}},
+ [{#ssh_msg_userauth_request{}=Req,Ssh}|Stack]) ->
+ {["AUTH srvr: Peer client authorized\n",
+ io_lib:format("user = ~p~n", [User]),
+ fmt_req(Req, Ssh)],
+ Stack};
+
+ssh_dbg_format(authentication, {return_from, {?MODULE,handle_userauth_request,3},
+ {not_authorized,{User,_X},_Repl}},
+ [{#ssh_msg_userauth_request{method="none"},Ssh}|Stack]) ->
+ Methods = Ssh#ssh.userauth_supported_methods,
+ {["AUTH srvr: Peer queries auth methods\n",
+ io_lib:format("user = ~p~nsupported methods = ~p ?", [User,Methods])
+ ],
+ Stack};
+
+ssh_dbg_format(authentication, {return_from, {?MODULE,handle_userauth_request,3},
+ {not_authorized,{User,_X}, Repl}
+ },
+ [{#ssh_msg_userauth_request{method = "publickey",
+ data = <<?BYTE(?FALSE), _/binary>>
+ }=Req,Ssh}|Stack]) ->
+ {case Repl of
+ {#ssh_msg_userauth_pk_ok{}, _} ->
+ ["AUTH srvr: Answer - pub key supported\n"];
+ {#ssh_msg_userauth_failure{}, _} ->
+ ["AUTH srvr: Answer - pub key not supported\n"];
+ {Other, _} ->
+ ["AUTH srvr: Answer - strange answer\n",
+ io_lib:format("strange answer = ~p~n",[Other])
+ ]
+ end
+ ++ [io_lib:format("user = ~p~n", [User]),
+ fmt_req(Req, Ssh)],
+ Stack};
+
+
+ssh_dbg_format(authentication, {call, {?MODULE,ssh_msg_userauth_result,[success]}},
+ Stack) ->
+ {["AUTH client: Success"],Stack};
+ssh_dbg_format(authentication, {return_from, {?MODULE,ssh_msg_userauth_result,1}, _Result},
+ Stack) ->
+ {skip, Stack};
+
+ssh_dbg_format(authentication, {return_from, {?MODULE,handle_userauth_request,3},
+ {not_authorized,{User,_X},_Repl}},
+ [{#ssh_msg_userauth_request{}=Req,Ssh}|Stack]) ->
+ {["AUTH srvr: Peer client authorization failed\n",
+ io_lib:format("user = ~p~n", [User]),
+ fmt_req(Req, Ssh)],
+ Stack};
+
+%%% Client ----------------
+ssh_dbg_format(authentication, {call, {?MODULE,init_userauth_request_msg, [#ssh{opts = Opts}]}},
+ Stack) ->
+ {["AUTH client: Service ssh-userauth accepted\n",
+ case ?GET_OPT(user, Opts) of
+ undefined ->
+ io_lib:format("user = undefined *** ERROR ***", []);
+ User ->
+ io_lib:format("user = ~p", [User])
+ end
+ ],
+ Stack};
+ssh_dbg_format(authentication, {return_from, {?MODULE,init_userauth_request_msg,1},
+ {Repl = #ssh_msg_userauth_request{user = User,
+ service = "ssh-connection",
+ method = "none"},
+ _Ssh}},
+ Stack) ->
+ {["AUTH client: Query for accepted methods\n",
+ io_lib:format("user = ~p", [User])],
+ [Repl|Stack]};
+
+ssh_dbg_format(authentication, {call, {?MODULE,userauth_request_msg,
+ [#ssh{userauth_methods = Methods}]}},
+ [ #ssh_msg_userauth_request{user = User,
+ service = "ssh-connection",
+ method = "none"} | Stack]) ->
+ {["AUTH client: Server supports\n",
+ io_lib:format("user = ~p~nmethods = ~p", [User,Methods])],
+ Stack};
+
+ssh_dbg_format(authentication, {call, {?MODULE,userauth_request_msg,[_Ssh]}},
+ Stack) ->
+ {skip,Stack};
+
+ssh_dbg_format(authentication, {return_from, {?MODULE,userauth_request_msg,1},
+ {send_disconnect, _Code, _Ssh}},
+ Stack) ->
+ {skip,Stack};
+ssh_dbg_format(authentication, {return_from, {?MODULE,userauth_request_msg,1},
+ {Method,{_Msg,_Ssh}}},
+ Stack) ->
+ {["AUTH client: Try auth with\n",
+ io_lib:format("method = ~p", [Method])],
+ Stack};
+
+
+
+ssh_dbg_format(authentication, Unhandled, Stack) ->
+ case Unhandled of
+ {call, {?MODULE,_F,_Args}} -> ok;
+ {return_from, {?MODULE,_F,_A}, _Resp} -> ok
+ end,
+ {["UNHANDLED AUTH FORMAT\n",
+ io_lib:format("Unhandled = ~p~nStack = ~p", [Unhandled,Stack])],
+ Stack}.
+
+
+%%% Dbg helpers ----------------
+
+
+fmt_req(#ssh_msg_userauth_request{user = User,
+ service = "ssh-connection",
+ method = Method,
+ data = Data},
+ #ssh{kb_tries_left = KbTriesLeft,
+ userauth_supported_methods = Methods}) ->
+ [io_lib:format("req user = ~p~n"
+ "req method = ~p~n"
+ "supported methods = ~p",
+ [User,Method,Methods]),
+ case Method of
+ "none" -> "";
+ "password" -> fmt_bool(Data);
+ "keyboard-interactive" -> fmt_kb_tries_left(KbTriesLeft);
+ "publickey" -> [case Data of
+ <<?BYTE(_), ?UINT32(ALen), Alg:ALen/binary, _/binary>> ->
+ io_lib:format("~nkey-type = ~p", [Alg]);
+ _ ->
+ ""
+ end];
+ _ -> ""
+ end].
+
+
+fmt_kb_tries_left(N) when is_integer(N)->
+ io_lib:format("~ntries left = ~p", [N-1]).
+
+
+fmt_bool(<<?BYTE(Bool),_/binary>>) ->
+ io_lib:format("~nBool = ~s",
+ [case Bool of
+ ?TRUE -> "true";
+ ?FALSE -> "false";
+ _ -> io_lib:format("? (~p)",[Bool])
+ end]);
+fmt_bool(<<>>) ->
+ "".
+
+
+
diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl
index d6c8cf84ed..729ce67968 100644
--- a/lib/ssh/src/ssh_cli.erl
+++ b/lib/ssh/src/ssh_cli.erl
@@ -663,6 +663,9 @@ ssh_dbg_off(terminate) -> dbg:ctpg(?MODULE, terminate, 2).
ssh_dbg_format(terminate, {call, {?MODULE,terminate, [Reason, State]}}) ->
["Cli Terminating:\n",
io_lib:format("Reason: ~p,~nState:~n~s", [Reason, wr_record(State)])
- ].
+ ];
+ssh_dbg_format(terminate, {return_from, {?MODULE,terminate,2}, _Ret}) ->
+ skip.
+
?wr_record(state).
diff --git a/lib/ssh/src/ssh_client_channel.erl b/lib/ssh/src/ssh_client_channel.erl
index 6de897e1b2..5ffdf49e9b 100644
--- a/lib/ssh/src/ssh_client_channel.erl
+++ b/lib/ssh/src/ssh_client_channel.erl
@@ -419,12 +419,16 @@ ssh_dbg_format(channels, {return_from, {?MODULE,init,1}, {stop,Reason}}) ->
["Server Channel Start FAILED!\n",
io_lib:format("Reason = ~p", [Reason])
];
+
ssh_dbg_format(channels, F) ->
ssh_dbg_format(terminate, F);
+
ssh_dbg_format(terminate, {call, {?MODULE,terminate, [Reason, State]}}) ->
["Server Channel Terminating:\n",
io_lib:format("Reason: ~p,~nState:~n~s", [Reason, wr_record(State)])
];
+ssh_dbg_format(terminate, {return_from, {?MODULE,terminate,2}, _Ret}) ->
+ skip;
ssh_dbg_format(channel_events, {call, {?MODULE,handle_call, [Call,From,State]}}) ->
[hdr("is called", State),
@@ -434,6 +438,7 @@ ssh_dbg_format(channel_events, {return_from, {?MODULE,handle_call,3}, Ret}) ->
["Server Channel call returned:\n",
io_lib:format("~p~n", [ssh_dbg:reduce_state(Ret)])
];
+
ssh_dbg_format(channel_events, {call, {?MODULE,handle_cast, [Cast,State]}}) ->
[hdr("got cast", State),
io_lib:format("Cast: ~p~n", [Cast])
@@ -442,6 +447,7 @@ ssh_dbg_format(channel_events, {return_from, {?MODULE,handle_cast,2}, Ret}) ->
["Server Channel cast returned:\n",
io_lib:format("~p~n", [ssh_dbg:reduce_state(Ret)])
];
+
ssh_dbg_format(channel_events, {call, {?MODULE,handle_info, [Info,State]}}) ->
[hdr("got info", State),
io_lib:format("Info: ~p~n", [Info])
diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl
index 3ad7c182d5..5e5ed9b79a 100644
--- a/lib/ssh/src/ssh_connection_handler.erl
+++ b/lib/ssh/src/ssh_connection_handler.erl
@@ -63,7 +63,8 @@
adjust_window/3, close/2,
disconnect/4,
get_print_info/1,
- set_sock_opts/2, get_sock_opts/2
+ set_sock_opts/2, get_sock_opts/2,
+ prohibited_sock_option/1
]).
-type connection_ref() :: ssh:connection_ref().
@@ -365,11 +366,11 @@ retrieve(ConnectionHandler, Key) ->
%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
set_sock_opts(ConnectionRef, SocketOptions) ->
try lists:foldr(fun({Name,_Val}, Acc) ->
- case lists:member(Name, [active, deliver, mode, packet]) of
+ case prohibited_sock_option(Name) of
true -> [Name|Acc];
false -> Acc
end
- end, [], SocketOptions)
+ end, [], SocketOptions)
of
[] ->
call(ConnectionRef, {set_sock_opts,SocketOptions});
@@ -380,6 +381,12 @@ set_sock_opts(ConnectionRef, SocketOptions) ->
{error, badarg}
end.
+prohibited_sock_option(active) -> true;
+prohibited_sock_option(deliver) -> true;
+prohibited_sock_option(mode) -> true;
+prohibited_sock_option(packet) -> true;
+prohibited_sock_option(_) -> false.
+
%%--------------------------------------------------------------------
%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
get_sock_opts(ConnectionRef, SocketGetOptions) ->
@@ -818,13 +825,13 @@ handle_event(_, #ssh_msg_kex_dh_gex_reply{} = Msg, {key_exchange_dh_gex_reply,cl
%%% ######## {new_keys, client|server} ####
%% First key exchange round:
-handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,client,init}, D) ->
- {ok, Ssh1} = ssh_transport:handle_new_keys(Msg, D#data.ssh_params),
+handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,client,init}, D0) ->
+ {ok, Ssh1} = ssh_transport:handle_new_keys(Msg, D0#data.ssh_params),
%% {ok, ExtInfo, Ssh2} = ssh_transport:ext_info_message(Ssh1),
- %% send_bytes(ExtInfo, D),
+ %% send_bytes(ExtInfo, D0),
{MsgReq, Ssh} = ssh_auth:service_request_msg(Ssh1),
- send_bytes(MsgReq, D),
- {next_state, {ext_info,client,init}, D#data{ssh_params=Ssh}};
+ D = send_msg(MsgReq, D0#data{ssh_params = Ssh}),
+ {next_state, {ext_info,client,init}, D};
handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,server,init}, D) ->
{ok, Ssh} = ssh_transport:handle_new_keys(Msg, D#data.ssh_params),
@@ -870,8 +877,8 @@ handle_event(_, Msg = #ssh_msg_service_request{name=ServiceName}, StateName = {s
"ssh-userauth" ->
Ssh0 = #ssh{session_id=SessionId} = D0#data.ssh_params,
{ok, {Reply, Ssh}} = ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0),
- send_bytes(Reply, D0),
- {next_state, {userauth,server}, D0#data{ssh_params = Ssh}};
+ D = send_msg(Reply, D0#data{ssh_params = Ssh}),
+ {next_state, {userauth,server}, D};
_ ->
{Shutdown, D} =
@@ -882,10 +889,12 @@ handle_event(_, Msg = #ssh_msg_service_request{name=ServiceName}, StateName = {s
end;
handle_event(_, #ssh_msg_service_accept{name = "ssh-userauth"}, {service_request,client},
- #data{ssh_params = #ssh{service="ssh-userauth"} = Ssh0} = State) ->
+ #data{ssh_params = #ssh{service="ssh-userauth"} = Ssh0} = D0) ->
{Msg, Ssh} = ssh_auth:init_userauth_request_msg(Ssh0),
- send_bytes(Msg, State),
- {next_state, {userauth,client}, State#data{auth_user = Ssh#ssh.user, ssh_params = Ssh}};
+ D = send_msg(Msg, D0#data{ssh_params = Ssh,
+ auth_user = Ssh#ssh.user
+ }),
+ {next_state, {userauth,client}, D};
%%% ######## {userauth, client|server} ####
@@ -901,8 +910,8 @@ handle_event(_,
%% Probably the very first userauth_request but we deny unauthorized login
{not_authorized, _, {Reply,Ssh}} =
ssh_auth:handle_userauth_request(Msg, Ssh0#ssh.session_id, Ssh0),
- send_bytes(Reply, D0),
- {keep_state, D0#data{ssh_params = Ssh}};
+ D = send_msg(Reply, D0#data{ssh_params = Ssh}),
+ {keep_state, D};
{"ssh-connection", "ssh-connection", Method} ->
%% Userauth request with a method like "password" or so
@@ -910,21 +919,24 @@ handle_event(_,
true ->
%% Yepp! we support this method
case ssh_auth:handle_userauth_request(Msg, Ssh0#ssh.session_id, Ssh0) of
- {authorized, User, {Reply, Ssh}} ->
- send_bytes(Reply, D0),
- D0#data.starter ! ssh_connected,
- connected_fun(User, Method, D0),
+ {authorized, User, {Reply, Ssh1}} ->
+ D = #data{ssh_params=Ssh} =
+ send_msg(Reply, D0#data{ssh_params = Ssh1}),
+ D#data.starter ! ssh_connected,
+ connected_fun(User, Method, D),
{next_state, {connected,server},
- D0#data{auth_user = User,
- ssh_params = Ssh#ssh{authenticated = true}}};
+ D#data{auth_user=User,
+ %% Note: authenticated=true MUST NOT be sent
+ %% before send_msg!
+ ssh_params = Ssh#ssh{authenticated = true}}};
{not_authorized, {User, Reason}, {Reply, Ssh}} when Method == "keyboard-interactive" ->
retry_fun(User, Reason, D0),
- send_bytes(Reply, D0),
- {next_state, {userauth_keyboard_interactive,server}, D0#data{ssh_params = Ssh}};
+ D = send_msg(Reply, D0#data{ssh_params = Ssh}),
+ {next_state, {userauth_keyboard_interactive,server}, D};
{not_authorized, {User, Reason}, {Reply, Ssh}} ->
retry_fun(User, Reason, D0),
- send_bytes(Reply, D0),
- {keep_state, D0#data{ssh_params = Ssh}}
+ D = send_msg(Reply, D0#data{ssh_params = Ssh}),
+ {keep_state, D}
end;
false ->
%% No we do not support this method (=/= none)
@@ -952,6 +964,7 @@ handle_event(_, #ssh_msg_ext_info{}=Msg, {userauth,client}, D0) ->
{keep_state, D};
handle_event(_, #ssh_msg_userauth_success{}, {userauth,client}, D=#data{ssh_params = Ssh}) ->
+ ssh_auth:ssh_msg_userauth_result(success),
D#data.starter ! ssh_connected,
{next_state, {connected,client}, D#data{ssh_params=Ssh#ssh{authenticated = true}}};
@@ -983,11 +996,11 @@ handle_event(_, #ssh_msg_userauth_failure{authentications = Methods}, StateName=
StateName, D0#data{ssh_params = Ssh}),
{stop, Shutdown, D};
{"keyboard-interactive", {Msg, Ssh}} ->
- send_bytes(Msg, D0),
- {next_state, {userauth_keyboard_interactive,client}, D0#data{ssh_params = Ssh}};
+ D = send_msg(Msg, D0#data{ssh_params = Ssh}),
+ {next_state, {userauth_keyboard_interactive,client}, D};
{_Method, {Msg, Ssh}} ->
- send_bytes(Msg, D0),
- {keep_state, D0#data{ssh_params = Ssh}}
+ D = send_msg(Msg, D0#data{ssh_params = Ssh}),
+ {keep_state, D}
end;
%%---- banner to client
@@ -1002,39 +1015,46 @@ handle_event(_, #ssh_msg_userauth_banner{message = Msg}, {userauth,client}, D) -
%%% ######## {userauth_keyboard_interactive, client|server}
handle_event(_, #ssh_msg_userauth_info_request{} = Msg, {userauth_keyboard_interactive, client},
- #data{ssh_params = Ssh0} = D) ->
+ #data{ssh_params = Ssh0} = D0) ->
case ssh_auth:handle_userauth_info_request(Msg, Ssh0) of
{ok, {Reply, Ssh}} ->
- send_bytes(Reply, D),
- {next_state, {userauth_keyboard_interactive_info_response,client}, D#data{ssh_params = Ssh}};
+ D = send_msg(Reply, D0#data{ssh_params = Ssh}),
+ {next_state, {userauth_keyboard_interactive_info_response,client}, D};
not_ok ->
- {next_state, {userauth,client}, D, [postpone]}
+ {next_state, {userauth,client}, D0, [postpone]}
end;
-handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_interactive, server}, D) ->
- case ssh_auth:handle_userauth_info_response(Msg, D#data.ssh_params) of
- {authorized, User, {Reply, Ssh}} ->
- send_bytes(Reply, D),
+handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_interactive, server}, D0) ->
+ case ssh_auth:handle_userauth_info_response(Msg, D0#data.ssh_params) of
+ {authorized, User, {Reply, Ssh1}} ->
+ D = #data{ssh_params=Ssh} =
+ send_msg(Reply, D0#data{ssh_params = Ssh1}),
D#data.starter ! ssh_connected,
connected_fun(User, "keyboard-interactive", D),
{next_state, {connected,server}, D#data{auth_user = User,
+ %% Note: authenticated=true MUST NOT be sent
+ %% before send_msg!
ssh_params = Ssh#ssh{authenticated = true}}};
{not_authorized, {User, Reason}, {Reply, Ssh}} ->
- retry_fun(User, Reason, D),
- send_bytes(Reply, D),
- {next_state, {userauth,server}, D#data{ssh_params = Ssh}};
+ retry_fun(User, Reason, D0),
+ D = send_msg(Reply, D0#data{ssh_params = Ssh}),
+ {next_state, {userauth,server}, D};
{authorized_but_one_more, _User, {Reply, Ssh}} ->
- send_bytes(Reply, D),
- {next_state, {userauth_keyboard_interactive_extra,server}, D#data{ssh_params = Ssh}}
+ D = send_msg(Reply, D0#data{ssh_params = Ssh}),
+ {next_state, {userauth_keyboard_interactive_extra,server}, D}
end;
-handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_interactive_extra, server}, D) ->
- {authorized, User, {Reply, Ssh}} = ssh_auth:handle_userauth_info_response({extra,Msg}, D#data.ssh_params),
- send_bytes(Reply, D),
+handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_interactive_extra, server}, D0) ->
+ {authorized, User, {Reply, Ssh1}} =
+ ssh_auth:handle_userauth_info_response({extra,Msg}, D0#data.ssh_params),
+ D = #data{ssh_params=Ssh} =
+ send_msg(Reply, D0#data{ssh_params = Ssh1}),
D#data.starter ! ssh_connected,
connected_fun(User, "keyboard-interactive", D),
{next_state, {connected,server}, D#data{auth_user = User,
+ %% Note: authenticated=true MUST NOT be sent
+ %% before send_msg!
ssh_params = Ssh#ssh{authenticated = true}}};
handle_event(_, #ssh_msg_userauth_failure{}, {userauth_keyboard_interactive, client},
@@ -1144,17 +1164,17 @@ handle_event(internal, {conn_msg,Msg}, StateName, #data{starter = User,
end;
-handle_event(enter, _OldState, {connected,_}=State, D) ->
+handle_event(enter, OldState, {connected,_}=NewState, D) ->
%% Entering the state where re-negotiation is possible
- init_renegotiate_timers(State, D);
+ init_renegotiate_timers(OldState, NewState, D);
-handle_event(enter, _OldState, {ext_info,_,renegotiate}=State, D) ->
+handle_event(enter, OldState, {ext_info,_,renegotiate}=NewState, D) ->
%% Could be hanging in exit_info state if nothing else arrives
- init_renegotiate_timers(State, D);
+ init_renegotiate_timers(OldState, NewState, D);
-handle_event(enter, {connected,_}, State, D) ->
+handle_event(enter, {connected,_}=OldState, NewState, D) ->
%% Exiting the state where re-negotiation is possible
- pause_renegotiate_timers(State, D);
+ pause_renegotiate_timers(OldState, NewState, D);
handle_event(cast, force_renegotiate, StateName, D) ->
handle_event({timeout,renegotiate}, undefined, StateName, D);
@@ -2112,25 +2132,32 @@ start_rekeying(Role, D0) ->
{next_state, {kexinit,Role,renegotiate}, D}.
-init_renegotiate_timers(State, D) ->
+init_renegotiate_timers(_OldState, NewState, D) ->
{RekeyTimeout,_MaxSent} = ?GET_OPT(rekey_limit, (D#data.ssh_params)#ssh.opts),
- {next_state, State, D, [{{timeout,renegotiate}, RekeyTimeout, none},
- {{timeout,check_data_size}, ?REKEY_DATA_TIMOUT, none} ]}.
+ {next_state, NewState, D, [{{timeout,renegotiate}, RekeyTimeout, none},
+ {{timeout,check_data_size}, ?REKEY_DATA_TIMOUT, none} ]}.
-pause_renegotiate_timers(State, D) ->
- {next_state, State, D, [{{timeout,renegotiate}, infinity, none},
- {{timeout,check_data_size}, infinity, none} ]}.
+pause_renegotiate_timers(_OldState, NewState, D) ->
+ {next_state, NewState, D, [{{timeout,renegotiate}, infinity, none},
+ {{timeout,check_data_size}, infinity, none} ]}.
check_data_rekeying(Role, D) ->
- {ok, [{send_oct,SocketSentTotal}]} = inet:getstat(D#data.socket, [send_oct]),
- SentSinceRekey = SocketSentTotal - D#data.last_size_rekey,
- {_RekeyTimeout,MaxSent} = ?GET_OPT(rekey_limit, (D#data.ssh_params)#ssh.opts),
- case check_data_rekeying_dbg(SentSinceRekey, MaxSent) of
- true ->
- start_rekeying(Role, D#data{last_size_rekey = SocketSentTotal});
- _ ->
- %% Not enough data sent for a re-negotiation. Restart timer.
+ case inet:getstat(D#data.socket, [send_oct]) of
+ {ok, [{send_oct,SocketSentTotal}]} ->
+ SentSinceRekey = SocketSentTotal - D#data.last_size_rekey,
+ {_RekeyTimeout,MaxSent} = ?GET_OPT(rekey_limit, (D#data.ssh_params)#ssh.opts),
+ case check_data_rekeying_dbg(SentSinceRekey, MaxSent) of
+ true ->
+ start_rekeying(Role, D#data{last_size_rekey = SocketSentTotal});
+ _ ->
+ %% Not enough data sent for a re-negotiation. Restart timer.
+ {keep_state, D, {{timeout,check_data_size}, ?REKEY_DATA_TIMOUT, none}}
+ end;
+ {error,_} ->
+ %% Socket closed, but before this module has handled that. Maybe
+ %% it is in the message queue.
+ %% Just go on like if there was not enough data transmitted to start re-keying:
{keep_state, D, {{timeout,check_data_size}, ?REKEY_DATA_TIMOUT, none}}
end.
@@ -2489,20 +2516,22 @@ ssh_dbg_flags(disconnect) -> [c].
ssh_dbg_on(connections) -> dbg:tp(?MODULE, init_connection_handler, 3, x),
ssh_dbg_on(terminate);
ssh_dbg_on(connection_events) -> dbg:tp(?MODULE, handle_event, 4, x);
-ssh_dbg_on(renegotiation) -> dbg:tpl(?MODULE, init_renegotiate_timers, 2, x),
- dbg:tpl(?MODULE, pause_renegotiate_timers, 2, x),
+ssh_dbg_on(renegotiation) -> dbg:tpl(?MODULE, init_renegotiate_timers, 3, x),
+ dbg:tpl(?MODULE, pause_renegotiate_timers, 3, x),
dbg:tpl(?MODULE, check_data_rekeying_dbg, 2, x),
- dbg:tpl(?MODULE, start_rekeying, 2, x);
+ dbg:tpl(?MODULE, start_rekeying, 2, x),
+ dbg:tp(?MODULE, renegotiate, 1, x);
ssh_dbg_on(terminate) -> dbg:tp(?MODULE, terminate, 3, x);
ssh_dbg_on(disconnect) -> dbg:tpl(?MODULE, send_disconnect, 7, x).
ssh_dbg_off(disconnect) -> dbg:ctpl(?MODULE, send_disconnect, 7);
ssh_dbg_off(terminate) -> dbg:ctpg(?MODULE, terminate, 3);
-ssh_dbg_off(renegotiation) -> dbg:ctpl(?MODULE, init_renegotiate_timers, 2),
- dbg:ctpl(?MODULE, pause_renegotiate_timers, 2),
+ssh_dbg_off(renegotiation) -> dbg:ctpl(?MODULE, init_renegotiate_timers, 3),
+ dbg:ctpl(?MODULE, pause_renegotiate_timers, 3),
dbg:ctpl(?MODULE, check_data_rekeying_dbg, 2),
- dbg:ctpl(?MODULE, start_rekeying, 2);
+ dbg:ctpl(?MODULE, start_rekeying, 2),
+ dbg:ctpg(?MODULE, renegotiate, 1);
ssh_dbg_off(connection_events) -> dbg:ctpg(?MODULE, handle_event, 4);
ssh_dbg_off(connections) -> dbg:ctpg(?MODULE, init_connection_handler, 3),
ssh_dbg_off(terminate).
@@ -2541,22 +2570,46 @@ ssh_dbg_format(connection_events, {return_from, {?MODULE,handle_event,4}, Ret})
io_lib:format("~p~n", [event_handler_result(Ret)])
];
-ssh_dbg_format(renegotiation, {call, {?MODULE,init_renegotiate_timers,[_State,D]}}) ->
- ["Renegotiation init\n",
- io_lib:format("rekey_limit: ~p ({ms,bytes})~ncheck_data_size: ~p (ms)~n",
- [?GET_OPT(rekey_limit, (D#data.ssh_params)#ssh.opts),
+ssh_dbg_format(renegotiation, {call, {?MODULE,init_renegotiate_timers,[OldState,NewState,D]}}) ->
+ ["Renegotiation: start timer (init_renegotiate_timers)\n",
+ io_lib:format("State: ~p --> ~p~n"
+ "rekey_limit: ~p ({ms,bytes})~n"
+ "check_data_size: ~p (ms)~n",
+ [OldState, NewState,
+ ?GET_OPT(rekey_limit, (D#data.ssh_params)#ssh.opts),
?REKEY_DATA_TIMOUT])
];
-ssh_dbg_format(renegotiation, {call, {?MODULE,pause_renegotiate_timers,[_State,_D]}}) ->
- ["Renegotiation pause\n"];
+ssh_dbg_format(renegotiation, {return_from, {?MODULE,init_renegotiate_timers,3}, _Ret}) ->
+ skip;
+
+ssh_dbg_format(renegotiation, {call, {?MODULE,renegotiate,[ConnectionHandler]}}) ->
+ ["Renegotiation: renegotiation forced\n",
+ io_lib:format("~p:renegotiate(~p) called~n",
+ [?MODULE,ConnectionHandler])
+ ];
+ssh_dbg_format(renegotiation, {return_from, {?MODULE,renegotiate,1}, _Ret}) ->
+ skip;
+
+ssh_dbg_format(renegotiation, {call, {?MODULE,pause_renegotiate_timers,[OldState,NewState,_D]}}) ->
+ ["Renegotiation: pause timers\n",
+ io_lib:format("State: ~p --> ~p~n",
+ [OldState, NewState])
+ ];
+ssh_dbg_format(renegotiation, {return_from, {?MODULE,pause_renegotiate_timers,3}, _Ret}) ->
+ skip;
+
ssh_dbg_format(renegotiation, {call, {?MODULE,start_rekeying,[_Role,_D]}}) ->
- ["Renegotiation start rekeying\n"];
+ ["Renegotiation: start rekeying\n"];
+ssh_dbg_format(renegotiation, {return_from, {?MODULE,start_rekeying,2}, _Ret}) ->
+ skip;
+
ssh_dbg_format(renegotiation, {call, {?MODULE,check_data_rekeying_dbg,[SentSinceRekey, MaxSent]}}) ->
- ["Renegotiation check data sent\n",
+ ["Renegotiation: check size of data sent\n",
io_lib:format("TotalSentSinceRekey: ~p~nMaxBeforeRekey: ~p~nStartRekey: ~p~n",
[SentSinceRekey, MaxSent, SentSinceRekey >= MaxSent])
];
-
+ssh_dbg_format(renegotiation, {return_from, {?MODULE,check_data_rekeying_dbg,2}, _Ret}) ->
+ skip;
ssh_dbg_format(terminate, {call, {?MODULE,terminate, [Reason, StateName, D]}}) ->
@@ -2591,6 +2644,8 @@ ssh_dbg_format(terminate, {call, {?MODULE,terminate, [Reason, StateName, D]}}) -
[Reason, StateName, ExtraInfo, state_data2proplist(D)])
]
end;
+ssh_dbg_format(renegotiation, {return_from, {?MODULE,terminate,3}, _Ret}) ->
+ skip;
ssh_dbg_format(disconnect, {call,{?MODULE,send_disconnect,
[Code, Reason, DetailedText, Module, Line, StateName, _D]}}) ->
@@ -2600,7 +2655,9 @@ ssh_dbg_format(disconnect, {call,{?MODULE,send_disconnect,
" DetailedText =~n"
" ~p",
[Module, Line, StateName, Code, Reason, lists:flatten(DetailedText)])
- ].
+ ];
+ssh_dbg_format(renegotiation, {return_from, {?MODULE,send_disconnect,7}, _Ret}) ->
+ skip.
event_handler_result({next_state, NextState, _NewData}) ->
diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl
index 1809b14099..f8391224f8 100644
--- a/lib/ssh/src/ssh_dbg.erl
+++ b/lib/ssh/src/ssh_dbg.erl
@@ -54,6 +54,8 @@
start_tracer/0, start_tracer/1,
on/1, on/0,
off/1, off/0,
+ is_on/0,
+ is_off/0,
go_on/0,
%% Circular buffer
cbuf_start/0, cbuf_start/1,
@@ -70,6 +72,9 @@
-export([init/1, handle_call/3, handle_cast/2, handle_info/2]).
+%% Internal apply_after:
+-export([ets_delete/2]).
+
-include("ssh.hrl").
-include("ssh_transport.hrl").
-include("ssh_connect.hrl").
@@ -82,12 +87,16 @@
-type trace_point() :: atom().
-type trace_points() :: [trace_point()].
+-type stack() :: list(term()).
-callback ssh_dbg_trace_points() -> trace_points().
-callback ssh_dbg_flags(trace_point()) -> [atom()].
-callback ssh_dbg_on(trace_point() | trace_points()) -> term().
-callback ssh_dbg_off(trace_point() | trace_points()) -> term().
--callback ssh_dbg_format(trace_point(), term()) -> iolist().
+-callback ssh_dbg_format(trace_point(), term()) -> iolist() | skip.
+-callback ssh_dbg_format(trace_point(), term(), stack()) -> {iolist() | skip, stack()}.
+
+-optional_callbacks([ssh_dbg_format/2, ssh_dbg_format/3]). % At least one of them are to be used
%%%================================================================
@@ -134,10 +143,13 @@ start_tracer(WriteFun, InitAcc) when is_function(WriteFun, 3) ->
%%%----------------------------------------------------------------
on() -> on(?ALL_DBG_TYPES).
on(Type) -> switch(on, Type).
-
+is_on() -> gen_server:call(?SERVER, get_on, ?CALL_TIMEOUT).
+
off() -> off(?ALL_DBG_TYPES). % A bit overkill...
off(Type) -> switch(off, Type).
+is_off() -> ?ALL_DBG_TYPES -- is_on().
+
go_on() ->
IsOn = gen_server:call(?SERVER, get_on, ?CALL_TIMEOUT),
@@ -174,8 +186,42 @@ reduce_state(T) ->
%%%----------------------------------------------------------------
init(_) ->
+ new_table(),
{ok, #data{}}.
+
+new_table() ->
+ try
+ ets:new(?MODULE, [public, named_table]),
+ ok
+ catch
+ exit:badarg ->
+ ok
+ end.
+
+
+get_proc_stack(Pid) when is_pid(Pid) ->
+ try ets:lookup_element(?MODULE, Pid, 2)
+ catch
+ error:badarg ->
+ %% Non-existing item
+ new_proc(Pid),
+ ets:insert(?MODULE, {Pid,[]}),
+ []
+ end.
+
+
+put_proc_stack(Pid, Data) when is_pid(Pid),
+ is_list(Data) ->
+ ets:insert(?MODULE, {Pid,Data}).
+
+
+new_proc(Pid) when is_pid(Pid) ->
+ gen_server:cast(?SERVER, {new_proc,Pid}).
+
+ets_delete(Tab, Key) ->
+ catch ets:delete(Tab, Key).
+
%%%----------------------------------------------------------------
handle_call({switch,on,Types}, _From, D) ->
NowOn = lists:usort(Types ++ D#data.types_on),
@@ -196,10 +242,20 @@ handle_call(C, _From, D) ->
{reply, {error,{unknown_call,C}}, D}.
+handle_cast({new_proc,Pid}, D) ->
+ monitor(process, Pid),
+ {noreply, D};
+
handle_cast(C, D) ->
io:format('*** Unknown cast: ~p~n',[C]),
{noreply, D}.
+
+handle_info({'DOWN', _MonitorRef, process, Pid, _Info}, D) ->
+ %% Universal real-time synchronization (there might be dbg msgs in the queue to the tracer):
+ timer:apply_after(20000, ?MODULE, ets_delete, [?MODULE, Pid]),
+ {noreply, D};
+
handle_info(C, D) ->
io:format('*** Unknown info: ~p~n',[C]),
{noreply, D}.
@@ -320,20 +376,60 @@ try_all_types_in_all_modules(TypesOn, Arg, WriteFun, Acc0) ->
TS = trace_ts(Arg),
PID = trace_pid(Arg),
INFO = trace_info(Arg),
- lists:foldl(
- fun(Type, Acc1) ->
- lists:foldl(
- fun(SshMod,Acc) ->
- try WriteFun("~n~s ~p ~s~n",
- [lists:flatten(TS),
- PID,
- lists:flatten(SshMod:ssh_dbg_format(Type, INFO))],
- Acc)
- catch
- _:_ -> Acc
- end
- end, Acc1, SshModules)
- end, Acc0, TypesOn).
+ Acc =
+ lists:foldl(
+ fun(Type, Acc1) ->
+ lists:foldl(
+ fun(SshMod,Acc) ->
+ try
+ %% First, call without stack
+ SshMod:ssh_dbg_format(Type, INFO)
+ of
+ skip ->
+ %% Don't try to print this later
+ written;
+ Txt when is_list(Txt) ->
+ write_txt(WriteFun, TS, PID, Txt)
+ catch
+ error:E when E==undef ; E==function_clause ; element(1,E)==case_clause ->
+ try
+ %% then, call with stack
+ STACK = get_proc_stack(PID),
+ SshMod:ssh_dbg_format(Type, INFO, STACK)
+ of
+ {skip, NewStack} ->
+ %% Don't try to print this later
+ put_proc_stack(PID, NewStack),
+ written;
+ {Txt, NewStack} when is_list(Txt) ->
+ put_proc_stack(PID, NewStack),
+ write_txt(WriteFun, TS, PID, Txt)
+ catch
+ _:_ ->
+ %% and finally, signal for special formatting
+ %% if noone else formats it
+ Acc
+ end
+ end
+ end, Acc1, SshModules)
+ end, Acc0, TypesOn),
+ case Acc of
+ Acc0 ->
+ %% INFO :: any()
+ WriteFun("~n~s ~p DEBUG~n~p~n", [lists:flatten(TS),PID,INFO], Acc0);
+ written ->
+ Acc0
+ end.
+
+
+
+write_txt(WriteFun, TS, PID, Txt) when is_list(Txt) ->
+ WriteFun("~n~s ~p ~s~n",
+ [lists:flatten(TS),
+ PID,
+ lists:flatten(Txt)],
+ written % this is returned
+ ).
%%%----------------------------------------------------------------
wr_record(T, Fs, BL) when is_tuple(T) ->
diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl
index 30b0c89144..804775bd75 100644
--- a/lib/ssh/src/ssh_message.erl
+++ b/lib/ssh/src/ssh_message.erl
@@ -762,15 +762,26 @@ ssh_dbg_format(ssh_messages, {call,{?MODULE,encode,[Msg]}}) ->
["Going to send ",Name,":\n",
wr_record(ssh_dbg:shrink_bin(Msg))
];
+ssh_dbg_format(ssh_messages, {return_from, {?MODULE,encode,1}, _Ret}) ->
+ skip;
+
+ssh_dbg_format(ssh_messages, {call, {?MODULE,decode,[_]}}) ->
+ skip;
ssh_dbg_format(ssh_messages, {return_from,{?MODULE,decode,1},Msg}) ->
Name = string:to_upper(atom_to_list(element(1,Msg))),
["Received ",Name,":\n",
wr_record(ssh_dbg:shrink_bin(Msg))
];
+
ssh_dbg_format(raw_messages, {call,{?MODULE,decode,[BytesPT]}}) ->
["Received plain text bytes (shown after decryption):\n",
io_lib:format("~p",[BytesPT])
];
+ssh_dbg_format(raw_messages, {return_from, {?MODULE,decode,1}, _Ret}) ->
+ skip;
+
+ssh_dbg_format(raw_messages, {call, {?MODULE,encode,[_]}}) ->
+ skip;
ssh_dbg_format(raw_messages, {return_from,{?MODULE,encode,1},BytesPT}) ->
["Going to send plain text bytes (shown before encryption):\n",
io_lib:format("~p",[BytesPT])
diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl
index cc9ef565d8..306eb86c23 100644
--- a/lib/ssh/src/ssh_options.erl
+++ b/lib/ssh/src/ssh_options.erl
@@ -261,22 +261,20 @@ config_val(Key, RoleCnfs, Opts) ->
check_fun(Key, Defs) ->
- #{chk := Fun} = maps:get(Key, Defs),
- Fun.
+ case ssh_connection_handler:prohibited_sock_option(Key) of
+ false ->
+ #{chk := Fun} = maps:get(Key, Defs),
+ Fun;
+ true ->
+ fun(_,_) -> forbidden end
+ end.
%%%================================================================
%%%
%%% Check and save one option
%%%
-
-%%% First some prohibited inet options:
-save({K,V}, _, _) when K == reuseaddr ;
- K == active
- ->
- forbidden_option(K, V);
-
-%%% then compatibility conversions:
+%%% First compatibility conversions:
save({allow_user_interaction,V}, Opts, Vals) ->
save({user_interaction,V}, Opts, Vals);
@@ -297,7 +295,12 @@ save({Key,Value}, Defs, OptMap) when is_map(OptMap) ->
{true, ModifiedValue} ->
OptMap#{Key := ModifiedValue};
false ->
- error({eoptions, {Key,Value}, "Bad value"})
+ error({eoptions, {Key,Value}, "Bad value"});
+ forbidden ->
+ error({eoptions, {Key,Value},
+ io_lib:format("The option '~s' is used internally. The "
+ "user is not allowed to specify this option.",
+ [Key])})
catch
%% An unknown Key (= not in the definition map) is
%% regarded as an inet option:
@@ -424,7 +427,8 @@ default(server) ->
check_string(S3) andalso
is_boolean(B);
(F) ->
- check_function3(F)
+ check_function3(F) orelse
+ check_function4(F)
end,
class => user_option
},
@@ -1180,10 +1184,3 @@ error_if_empty([]) ->
ok.
%%%----------------------------------------------------------------
-forbidden_option(K,V) ->
- Txt = io_lib:format("The option '~s' is used internally. The "
- "user is not allowed to specify this option.",
- [K]),
- error({eoptions, {K,V}, Txt}).
-
-%%%----------------------------------------------------------------
diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl
index 9ee6ed68c1..69fff09979 100644
--- a/lib/ssh/src/ssh_sftp.erl
+++ b/lib/ssh/src/ssh_sftp.erl
@@ -1843,7 +1843,9 @@ ssh_dbg_off(terminate) -> dbg:ctpg(?MODULE, terminate, 2).
ssh_dbg_format(terminate, {call, {?MODULE,terminate, [Reason, State]}}) ->
["Sftp Terminating:\n",
io_lib:format("Reason: ~p,~nState:~n~s", [Reason, wr_record(State)])
- ].
+ ];
+ssh_dbg_format(terminate, {return_from, {?MODULE,terminate,2}, _Ret}) ->
+ skip.
?wr_record(state).
diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl
index 78277705d8..158ef3d5ae 100644
--- a/lib/ssh/src/ssh_sftpd.erl
+++ b/lib/ssh/src/ssh_sftpd.erl
@@ -964,6 +964,8 @@ ssh_dbg_off(terminate) -> dbg:ctpg(?MODULE, terminate, 2).
ssh_dbg_format(terminate, {call, {?MODULE,terminate, [Reason, State]}}) ->
["SftpD Terminating:\n",
io_lib:format("Reason: ~p,~nState:~n~s", [Reason, wr_record(State)])
- ].
+ ];
+ssh_dbg_format(terminate, {return_from, {?MODULE,terminate,2}, _Ret}) ->
+ skip.
?wr_record(state).
diff --git a/lib/ssh/src/ssh_shell.erl b/lib/ssh/src/ssh_shell.erl
index b5862b2395..be8a6aa8cc 100644
--- a/lib/ssh/src/ssh_shell.erl
+++ b/lib/ssh/src/ssh_shell.erl
@@ -200,6 +200,8 @@ ssh_dbg_off(terminate) -> dbg:ctpg(?MODULE, terminate, 2).
ssh_dbg_format(terminate, {call, {?MODULE,terminate, [Reason, State]}}) ->
["Shell Terminating:\n",
io_lib:format("Reason: ~p,~nState:~n~s", [Reason, wr_record(State)])
- ].
+ ];
+ssh_dbg_format(terminate, {return_from, {?MODULE,terminate,2}, _Ret}) ->
+ skip.
?wr_record(state).
diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl
index 86d4ef951c..bd901c99e0 100644
--- a/lib/ssh/src/ssh_transport.erl
+++ b/lib/ssh/src/ssh_transport.erl
@@ -2172,18 +2172,27 @@ ssh_dbg_off(ssh_messages) -> ssh_dbg_off(hello).
+ssh_dbg_format(hello, {call,{?MODULE,hello_version_msg,[_]}}) ->
+ skip;
ssh_dbg_format(hello, {return_from,{?MODULE,hello_version_msg,1},Hello}) ->
["Going to send hello message:\n",
Hello
];
+
ssh_dbg_format(hello, {call,{?MODULE,handle_hello_version,[Hello]}}) ->
["Received hello message:\n",
Hello
];
+ssh_dbg_format(hello, {return_from,{?MODULE,handle_hello_version,1},_Ret}) ->
+ skip;
+
+ssh_dbg_format(alg, {call,{?MODULE,select_algorithm,[_,_,_,_]}}) ->
+ skip;
ssh_dbg_format(alg, {return_from,{?MODULE,select_algorithm,4},{ok,Alg}}) ->
["Negotiated algorithms:\n",
wr_record(Alg)
];
+
ssh_dbg_format(raw_messages, X) -> ssh_dbg_format(hello, X);
ssh_dbg_format(ssh_messages, X) -> ssh_dbg_format(hello, X).
diff --git a/lib/ssh/test/.gitignore b/lib/ssh/test/.gitignore
index c9d5f086b3..f0adbaf33f 100644
--- a/lib/ssh/test/.gitignore
+++ b/lib/ssh/test/.gitignore
@@ -1,4 +1,6 @@
+*COVER.html
+ssh_sftp_SUITE_data/test_data*
property_test/ssh_eqc_client_server_dirs/system
property_test/ssh_eqc_client_server_dirs/user
diff --git a/lib/ssh/test/Makefile b/lib/ssh/test/Makefile
index aafec6566e..9862071b5f 100644
--- a/lib/ssh/test/Makefile
+++ b/lib/ssh/test/Makefile
@@ -46,6 +46,7 @@ MODULES= \
ssh_protocol_SUITE \
ssh_property_test_SUITE \
ssh_pubkey_SUITE \
+ ssh_renegotiate_SUITE \
ssh_sftp_SUITE \
ssh_sftpd_SUITE \
ssh_sftpd_erlclient_SUITE \
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server.erl b/lib/ssh/test/property_test/ssh_eqc_client_server.erl
index e0b739ab53..66a79c8a17 100644
--- a/lib/ssh/test/property_test/ssh_eqc_client_server.erl
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server.erl
@@ -480,12 +480,9 @@ setup_rsa(Dir) ->
erase_dir(user_dir(Dir)),
file:make_dir(system_dir(Dir)),
file:make_dir(user_dir(Dir)),
-
- file:copy(data_dir(Dir,"id_rsa"), user_dir(Dir,"id_rsa")),
- file:copy(data_dir(Dir,"ssh_host_rsa_key"), system_dir(Dir,"ssh_host_rsa_key")),
- file:copy(data_dir(Dir,"ssh_host_rsa_key"), system_dir(Dir,"ssh_host_rsa_key.pub")),
- ssh_test_lib:setup_rsa_known_host(data_dir(Dir), user_dir(Dir)),
- ssh_test_lib:setup_rsa_auth_keys(data_dir(Dir), user_dir(Dir)).
+ ct:log("Dir = ~p~ndata_dir = ~p~nsystem_dir = ~p~nuser = ~p~n",
+ [Dir,data_dir(Dir),system_dir(Dir),user_dir(Dir)]),
+ ssh_test_lib:setup_all_user_host_keys( data_dir(Dir), user_dir(Dir), system_dir(Dir)).
data_dir(Dir, File) -> filename:join(Dir, File).
system_dir(Dir, File) -> filename:join([Dir, "system", File]).
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_dsa b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_dsa
index d306f8b26e..24628e071b 100644
--- a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_dsa
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_dsa
@@ -1,13 +1,12 @@
-----BEGIN DSA PRIVATE KEY-----
-MIIBvAIBAAKBgQDfi2flSTZZofwT4yQT0NikX/LGNT7UPeB/XEWe/xovEYCElfaQ
-APFixXvEgXwoojmZ5kiQRKzLM39wBP0jPERLbnZXfOOD0PDnw0haMh7dD7XKVMod
-/EigVgHf/qBdM2M8yz1s/rRF7n1UpLSypziKjkzCm7JoSQ2zbWIPdmBIXwIVAMgP
-kpr7Sq3O7sHdb8D601DRjoExAoGAMOQxDfB2Fd8ouz6G96f/UOzRMI/Kdv8kYYKW
-JIGY+pRYrLPyYzUeJznwZreOJgrczAX+luHnKFWJ2Dnk5CyeXk67Wsr7pJ/4MBMD
-OKeIS0S8qoSBN8+Krp79fgA+yS3IfqbkJLtLu4EBaCX4mKQIX4++k44d4U5lc8pt
-+9hlEI8CgYEAznKxx9kyC6bVo7LUYKaGhofRFt0SYFc5PVmT2VUGRs1R6+6DPD+e
-uEO6IhFct7JFSRbP9p0JD4Uk+3zlZF+XX6b2PsZkeV8f/02xlNGUSmEzCSiNg1AX
-Cy/WusYhul0MncWCHMcOZB5rIvU/aP5EJJtn3xrRaz6u0SThF6AnT34CFQC63czE
-ZU8w8Q+H7z0j+a+70x2iAw==
+MIIBuwIBAAKBgQDIywHurUpOq6kZuMn+XlRzR4hAxF6qwSkuEqkV7iHnLQ0kIwf3
+uAmjFDhuEsQ8653SLxGVvTNp+KFFgDXiLqgM7TPUwDnpbvzEZHPAU+/zPt4sdY2D
+txBfJwT2SFlK6HPOxOcxdDuD+/a59sh8hk/YVOU7ZTcBVsVG8Got4UcF5QIVAPGd
+CPDQKSTlPiM9OwBB1+9p11k5AoGARLxw4l17mET9cU0uf4Ppe5nsCbODJv44ZrSs
+picvypGVLrLcN5KWbm3vjRFCQ5LFunAG3FwLC2Sh0CH6TemoIfRPsRHR7wvpBGdr
+c693UlMOis/mcmvNMQAzuQNW9WrxdzsvWR/r5s6NEHWqKUJGXSPi2d+Ijq/mCOmI
+hzLzyiACgYEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4
+cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+C
+ROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNgCFEjA7wTC
+sQCY/I35vb6GUJn9tEdP
-----END DSA PRIVATE KEY-----
-
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_dsa.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_dsa.pub
new file mode 100644
index 0000000000..018ef6f537
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_dsa.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAMjLAe6tSk6rqRm4yf5eVHNHiEDEXqrBKS4SqRXuIectDSQjB/e4CaMUOG4SxDzrndIvEZW9M2n4oUWANeIuqAztM9TAOelu/MRkc8BT7/M+3ix1jYO3EF8nBPZIWUroc87E5zF0O4P79rn2yHyGT9hU5TtlNwFWxUbwai3hRwXlAAAAFQDxnQjw0Ckk5T4jPTsAQdfvaddZOQAAAIBEvHDiXXuYRP1xTS5/g+l7mewJs4Mm/jhmtKymJy/KkZUustw3kpZube+NEUJDksW6cAbcXAsLZKHQIfpN6agh9E+xEdHvC+kEZ2tzr3dSUw6Kz+Zya80xADO5A1b1avF3Oy9ZH+vmzo0QdaopQkZdI+LZ34iOr+YI6YiHMvPKIAAAAIEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+CROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNg= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa256 b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa256
new file mode 100644
index 0000000000..4b1eb12eaa
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa256
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIJfCaBKIIKhjbJl5F8BedqlXOQYDX5ba9Skypllmx/w+oAoGCCqGSM49
+AwEHoUQDQgAE49RbK2xQ/19ji3uDPM7uT4692LbwWF1TiaA9vUuebMGazoW/98br
+N9xZu0L1AWwtEjs3kmJDTB7eJEGXnjUAcQ==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa256.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa256.pub
new file mode 100644
index 0000000000..a0147e60fa
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa256.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOPUWytsUP9fY4t7gzzO7k+Ovdi28FhdU4mgPb1LnmzBms6Fv/fG6zfcWbtC9QFsLRI7N5JiQ0we3iRBl541AHE= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa384 b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa384
new file mode 100644
index 0000000000..4e8aa40959
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa384
@@ -0,0 +1,6 @@
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDCYXb6OSAZyXRfLXOtMo43za197Hdc/T0YKjgQQjwDt6rlRwqTh7v7S
+PV2kXwNGdWigBwYFK4EEACKhZANiAARN2khlJUOOIiwsWHEALwDieeZR96qL4pUd
+ci7aeGaczdUK5jOA9D9zmBZtSYTfO8Cr7ekVghDlcWAIJ/BXcswgQwSEQ6wyfaTF
+8FYfyr4l3u9IirsnyaFzeIgeoNis8Gw=
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa384.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa384.pub
new file mode 100644
index 0000000000..41e722e545
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa384.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBE3aSGUlQ44iLCxYcQAvAOJ55lH3qovilR1yLtp4ZpzN1QrmM4D0P3OYFm1JhN87wKvt6RWCEOVxYAgn8FdyzCBDBIRDrDJ9pMXwVh/KviXe70iKuyfJoXN4iB6g2KzwbA== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa521 b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa521
new file mode 100644
index 0000000000..7196f46e97
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa521
@@ -0,0 +1,7 @@
+-----BEGIN EC PRIVATE KEY-----
+MIHbAgEBBEFMadoz4ckEcClfqXa2tiUuYkJdDfwq+/iFQcpt8ESuEd26IY/vm47Q
+9UzbPkO4ou8xkNsQ3WvCRQBBWtn5O2kUU6AHBgUrgQQAI6GBiQOBhgAEAde5BRu5
+01/jS0jRk212xsb2DxPrxNpgp6IMCV8TA4Eps+8bSqHB091nLiBcP422HXYfuCd7
+XDjSs8ihcmhp0hCRASLqZR9EzW9W/SOt876May1Huj5X+WSO6RLe7vPn9vmf7kHf
+pip6m7M7qp2qGgQ3q2vRwS2K/O6156ohiOlmuuFs
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa521.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa521.pub
new file mode 100644
index 0000000000..8f059120bc
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa521.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAHXuQUbudNf40tI0ZNtdsbG9g8T68TaYKeiDAlfEwOBKbPvG0qhwdPdZy4gXD+Nth12H7gne1w40rPIoXJoadIQkQEi6mUfRM1vVv0jrfO+jGstR7o+V/lkjukS3u7z5/b5n+5B36YqepuzO6qdqhoEN6tr0cEtivzuteeqIYjpZrrhbA== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed25519 b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed25519
new file mode 100644
index 0000000000..401a3e4a9a
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed25519
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACDm9P8/gC0IOKmwHLSvkmEtS2Xx0RRqUDqC6wY6UgDVnwAAAJg3+6xpN/us
+aQAAAAtzc2gtZWQyNTUxOQAAACDm9P8/gC0IOKmwHLSvkmEtS2Xx0RRqUDqC6wY6UgDVnw
+AAAEBzC/Z2WGJhZ3l3tIBnUc6DCbp+lXY2yc2RRpWQTdf8sub0/z+ALQg4qbActK+SYS1L
+ZfHRFGpQOoLrBjpSANWfAAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed25519.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed25519.pub
new file mode 100644
index 0000000000..a5c03b19c1
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed25519.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOb0/z+ALQg4qbActK+SYS1LZfHRFGpQOoLrBjpSANWf uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed448 b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed448
new file mode 100644
index 0000000000..8ecfd710dc
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed448
@@ -0,0 +1,10 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz
+c2gtZWQ0NDgAAAA53OqeePNaG/NJmoMbELhskKrAHNhLZ6AQm1WjbpMoseNl/OFh
+1xznExpUPqTLX36fHYsAaWRHABQAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA
+ADnc6p5481ob80magxsQuGyQqsAc2EtnoBCbVaNukyix42X84WHXHOcTGlQ+pMtf
+fp8diwBpZEcAFAAAAAByzSPST3FCdOdENDI3uTKQ9RH2Ql+Y5kRZ/yA+iYUIP/32
+BQBVOrwOBc0CGEvbicTM1n4YeVEmfrMo3OqeePNaG/NJmoMbELhskKrAHNhLZ6AQ
+m1WjbpMoseNl/OFh1xznExpUPqTLX36fHYsAaWRHABQAAAAAAAECAwQ=
+-----END OPENSSH PRIVATE KEY-----
+
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed448.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed448.pub
new file mode 100644
index 0000000000..cec0765a5d
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed448.pub
@@ -0,0 +1 @@
+ssh-ed448 AAAACXNzaC1lZDQ0OAAAADnc6p5481ob80magxsQuGyQqsAc2EtnoBCbVaNukyix42X84WHXHOcTGlQ+pMtffp8diwBpZEcAFAA= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_rsa b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_rsa
index 9d7e0dd5fb..2202c2ead8 100644
--- a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_rsa
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_rsa
@@ -1,15 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
-MIICXAIBAAKBgQD1OET+3O/Bvj/dtjxDTXmj1oiJt4sIph5kGy0RfjoPrZfaS+CU
-DhakCmS6t2ivxWFgtpKWaoGMZMJqWj6F6ZsumyFl3FPBtujwY/35cgifrI9Ns4Tl
-zR1uuengNBmV+WRQ5cd9F2qS6Z8aDQihzt0r8JUqLcK+VQbrmNzboCCQQwIDAQAB
-AoGAPQEyqPTt8JUT7mRXuaacjFXiweAXhp9NEDpyi9eLOjtFe9lElZCrsUOkq47V
-TGUeRKEm9qSodfTbKPoqc8YaBJGJPhUaTAcha+7QcDdfHBvIsgxvU7ePVnlpXRp3
-CCUEMPhlnx6xBoTYP+fRU0e3+xJIPVyVCqX1jAdUMkzfRoECQQD6ux7B1QJAIWyK
-SGkbDUbBilNmzCFNgIpOP6PA+bwfi5d16diTpra5AX09keQABAo/KaP1PdV8Vg0p
-z4P3A7G3AkEA+l+AKG6m0kQTTBMJDqOdVPYwe+5GxunMaqmhokpEbuGsrZBl5Dvd
-WpcBjR7jmenrhKZRIuA+Fz5HPo/UQJPl1QJBAKxstDkeED8j/S2XoFhPKAJ+6t39
-sUVICVTIZQeXdmzHJXCcUSkw8+WEhakqw/3SyW0oaK2FSWQJFWJUZ+8eJj8CQEh3
-xeduB5kKnS9CvzdeghZqX6QvVosSdtlUmfUYW/BgH5PpHKTP8wTaeld3XldZTpMJ
-dKiMkUw2+XYROVUrubUCQD+Na1LhULlpn4ISEtIEfqpdlUhxDgO15Wg8USmsng+x
-ICliVOSQtwaZjm8kwaFt0W7XnpnDxbRs37vIEbIMWak=
+MIIEpAIBAAKCAQEAztjiyj2tdfkji0fewWS0kABg0IABgG20NvL1PnHJLr98we7w
+W7f3j27EGjW/ApuycsWXXKi0L82q8uDicoHHb3JI2JkT70oi0yG1Dx/zwPN+dkA7
+LBT1J3UK2hJTFPhp855CwY/ss9xpBsd1Fv3zuHifEqNGljeg1PjmQ3pNhxA/M0aZ
+cLnfIUdZ5Hr+t+4es3zaWo4tLBKmZu6BkVGQKPGXeMkIAMtJlG24l7qKDRkR5TYA
+ZT7P8Vn7hnuFuCNbrJSm686GawBxTQXom23dg9UcWxoHB7UiHFoR6j0bQAX+4R7b
+IwculRDcvzrgCu6u06oFILwY7MlsxpX9hGTl1wIDAQABAoIBAFeP6pmQeICrYceR
+OhQGLIWVE2bP+VLDnflw6i5v/qlieE6kdm1tOEgorK0nuV9CR81cJdIcvIJL/yTn
+3BR7KdDcwUenrY+rg4h7CWmIrigtK4ilciccDBeS7XAZN8B11GxDv6Cu65XMJU2w
+W7nK8URTE4vRQI1QqS3e26MPAAi/LVOt3ZPI6zg/GHEwnq0IVSQAOndLBr/IWZk5
+SANrkfwX8WS7/UxZgDptT9dyUQ5Pnj5mieTlIvBwyczdhZ7RDa8HdCSHW3xF83V1
+A0pkn6+TRojumYyr4RrPQj6htE64Hgx9w1Dv/UINjPXl5mGlbxQHMWGzlqD/qpyI
+wg7RakECgYEA+9ARZpHfEFz+EEFi8l9J+BtJDo00WaKCOZHh5UJ8W+NreqSd8nSx
+5u6wYwMJjRX2Hwv+FBEhxGbo1+ff6p++cYmiSlDtN2XRCDkBWvvGlxu55BDULrhx
+f8lqaV3XGmOy2rQusp8hiHmkmPJCSVj3oJqQnbqJ2zahXAx1rTPwHqECgYEA0kln
+4h+ZkZ+aldOMGF0d0txTcTqZvsSVKiFTSD9of/fiSDqb6xtLT2+ys6FZoFL9lyK8
+gtqH642CDQ+3WT6Nmn4kMF5HNVpEuCeRDeRhiquWeKaAQDyvZ5ym1+Cn3GhsO7Di
+d2LJKV5hOoN77loVY5nwnUVIJ0h+WLf0T7DTCXcCgYEAiNT7X50MdTvS4splFgcp
+jqRlAn9AXySrVtUqxwVlxhjCIpapLUK0GSTCvEq+OeghIaXGnujgTHUPOaNKTZgY
+SGHdyjxHar7s42b2kZYWx63NSVzLr8eSBTpRlIflhvV+DtGyPmWyNxLCmkmqM2kg
+xii3RL5EgtYgwIAUwdVjOYECgYBRPlsMWfkS8f7fc+PkZdVn6gey71kHAxw+MrHi
+b90H09Vw4nPq2Zi3EAiSrfvanTWsdpcuVw+8See89B16NViwH5wLs+D/E+kI3QCF
+xX6J/NEdu/ZA2zFJbpRnQzyXQyDNzwEv7tKZUQVvfe0boWIyIP99Q48k3jUyQZ/6
+Se6+8QKBgQCXl8H2K3CsZxoujKLb2qoEOPbxJQ2hxoMTS5XuQECReIVsNuptWrur
+DF8WJi/B6AqwRX1P3l56RNwqB1yDBqv0QVLpU7vU/FmWqLWTn0r3AvM74qftvfAE
+oa31wcYoCqPJoKgCG7TThLhNt2v5hL7sVgZNO0ueAiHhJbFLaf7ceg==
-----END RSA PRIVATE KEY-----
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_rsa.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_rsa.pub
new file mode 100644
index 0000000000..b4084d320a
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_rsa.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDO2OLKPa11+SOLR97BZLSQAGDQgAGAbbQ28vU+cckuv3zB7vBbt/ePbsQaNb8Cm7JyxZdcqLQvzary4OJygcdvckjYmRPvSiLTIbUPH/PA8352QDssFPUndQraElMU+GnznkLBj+yz3GkGx3UW/fO4eJ8So0aWN6DU+OZDek2HED8zRplwud8hR1nkev637h6zfNpaji0sEqZm7oGRUZAo8Zd4yQgAy0mUbbiXuooNGRHlNgBlPs/xWfuGe4W4I1uslKbrzoZrAHFNBeibbd2D1RxbGgcHtSIcWhHqPRtABf7hHtsjBy6VENy/OuAK7q7TqgUgvBjsyWzGlf2EZOXX uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key256 b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key256
new file mode 100644
index 0000000000..2979ea88ed
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key256
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIMe4MDoit0t8RzSVPwkCBemQ9fhXL+xnTSAWISw8HNCioAoGCCqGSM49
+AwEHoUQDQgAEo2q7U3P6r0W5WGOLtM78UQtofM9UalEhiZeDdiyylsR/RR17Op0s
+VPGSADLmzzgcucLEKy17j2S+oz42VUJy5A==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key256.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key256.pub
new file mode 100644
index 0000000000..85dc419345
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key256.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKNqu1Nz+q9FuVhji7TO/FELaHzPVGpRIYmXg3YsspbEf0UdezqdLFTxkgAy5s84HLnCxCste49kvqM+NlVCcuQ= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key384 b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key384
new file mode 100644
index 0000000000..fb1a862ded
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key384
@@ -0,0 +1,6 @@
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDArxbDfh3p1okrD9wQw6jJ4d4DdlBPD5GqXE8bIeRJiK41Sh40LgvPw
+mkqEDSXK++CgBwYFK4EEACKhZANiAAScl43Ih2lWTDKrSox5ve5uiTXil4smsup3
+CfS1XPjKxgBAmlfBim8izbdrT0BFdQzz2joduNMtpt61wO4rGs6jm0UP7Kim9PC7
+Hneb/99fIYopdMH5NMnk60zGO1uZ2vc=
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key384.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key384.pub
new file mode 100644
index 0000000000..428d5fb7d7
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key384.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBJyXjciHaVZMMqtKjHm97m6JNeKXiyay6ncJ9LVc+MrGAECaV8GKbyLNt2tPQEV1DPPaOh240y2m3rXA7isazqObRQ/sqKb08Lsed5v/318hiil0wfk0yeTrTMY7W5na9w== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key521 b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key521
new file mode 100644
index 0000000000..3e51ec2ecd
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key521
@@ -0,0 +1,7 @@
+-----BEGIN EC PRIVATE KEY-----
+MIHcAgEBBEIB8O1BFkl2HQjQLRLonEZ97da/h39DMa9/0/hvPZWAI8gUPEQcHxRx
+U7b09p3Zh+EBbMFq8+1ae9ds+ZTxE4WFSvKgBwYFK4EEACOhgYkDgYYABAAlWVjq
+Bzg7Wt4gE6UNb1lRE2cnlmH2L/A5uo6qZRx5lPnSKOxEhxSb/Oay1+9d6KRdrh6/
+vlhd9SHDBhLcAPDvWgBnJIEj92Q3pXX4JtoitL0yl+SvvU+vUh966mzHShHzj8p5
+ccOgPkPNoA70yrpGzkIhPezpZOQdCaOXj/jFqNCTDg==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key521.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key521.pub
new file mode 100644
index 0000000000..017a29f4da
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key521.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAAlWVjqBzg7Wt4gE6UNb1lRE2cnlmH2L/A5uo6qZRx5lPnSKOxEhxSb/Oay1+9d6KRdrh6/vlhd9SHDBhLcAPDvWgBnJIEj92Q3pXX4JtoitL0yl+SvvU+vUh966mzHShHzj8p5ccOgPkPNoA70yrpGzkIhPezpZOQdCaOXj/jFqNCTDg== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed25519_key b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed25519_key
new file mode 100644
index 0000000000..13a8fcf491
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed25519_key
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQAAAJi+h4O7voeD
+uwAAAAtzc2gtZWQyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQ
+AAAEBaOcJfGPNemKc1wPHTCmM4Kwvh6dZ0CqY14UT361UnN0lI66JgZZo72XJ7wGBp+h3W
+TDw/pxXddvaomAIHrIl9AAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed25519_key.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed25519_key.pub
new file mode 100644
index 0000000000..156ef4045c
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed25519_key.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIElI66JgZZo72XJ7wGBp+h3WTDw/pxXddvaomAIHrIl9 uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed448_key b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed448_key
new file mode 100644
index 0000000000..31a7e4e8c3
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed448_key
@@ -0,0 +1,10 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz
+c2gtZWQ0NDgAAAA5X9dEm1m0Yf0s54fsYWrUah2hNCSFpw4fig6nXYDpZ3jt8SR2
+m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA
+ADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHl
+D2zR+hq+r+glYYAAAABybIKlYsuAjRDWMr6JyFE+v2ySnzTd+oyfY8mWDvbjSKNS
+jIo/zC8ETjmj/FuUSS+PAy51SaIAmPlbX9dEm1m0Yf0s54fsYWrUah2hNCSFpw4f
+ig6nXYDpZ3jt8SR2m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAAAAECAwQ=
+-----END OPENSSH PRIVATE KEY-----
+
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed448_key.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed448_key.pub
new file mode 100644
index 0000000000..8c390dcb58
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed448_key.pub
@@ -0,0 +1 @@
+ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA=
diff --git a/lib/ssh/test/ssh_agent_SUITE.erl b/lib/ssh/test/ssh_agent_SUITE.erl
index 7efd1c6d88..836a61d389 100644
--- a/lib/ssh/test/ssh_agent_SUITE.erl
+++ b/lib/ssh/test/ssh_agent_SUITE.erl
@@ -48,6 +48,22 @@ init_per_suite(Config) ->
end_per_suite(_Config) ->
ok = ssh:stop().
+init_per_testcase(connect_with_ssh_agent, Config) ->
+ DataDir = proplists:get_value(data_dir, Config),
+ UserDir = proplists:get_value(priv_dir, Config),
+ AgentUserDir = filename:join(UserDir,"agent"), % just to separate them in the tests
+ % so we know that the right dir is used
+ file:make_dir(AgentUserDir),
+ %% Arrange the host keys in <priv_dir>/system
+ ct:log("Host keys setup for: ~p",
+ [ssh_test_lib:setup_all_host_keys(Config)]),
+ %% Copy the user's files used by the daemon
+ {ok,_} = file:copy(filename:join(DataDir,"authorized_keys"),
+ filename:join(UserDir,"authorized_keys")),
+ %% And copy the user's files used by the agent (and not by the user)
+ {ok,_} = file:copy(filename:join(DataDir,"id_rsa"),
+ filename:join(AgentUserDir,"id_rsa")),
+ Config;
init_per_testcase(_TestCase, Config) ->
Config.
@@ -119,16 +135,18 @@ connect_with_ssh_agent() ->
[{doc, "Connect with RSA key from SSH agent"}].
connect_with_ssh_agent(Config) ->
- DataDir = proplists:get_value(data_dir, Config),
- {ok, SocketPath} = ssh_agent_mock_server:start_link('rsa-sha2-256', DataDir),
- {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, DataDir},
- {user_dir, DataDir}]),
- ConnectionRef = ssh_test_lib:connect(Host, Port, [{user_dir, DataDir},
- {silently_accept_hosts, true},
- {user_interaction, false},
- {auth_methods, "publickey"},
- {key_cb, {ssh_agent, [{socket_path, SocketPath}]}}
- ]),
+ UserDir = PrivDir = proplists:get_value(priv_dir, Config),
+ AgentUserDir = filename:join(UserDir,"agent"),
+ SystemDir = filename:join(PrivDir, "system"),
+ {ok, SocketPath} = ssh_agent_mock_server:start_link('rsa-sha2-256', AgentUserDir),
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
+ {user_dir, UserDir}]),
+ ConnectionRef = ssh_test_lib:connect(Host, Port, [{user_dir, UserDir},
+ {silently_accept_hosts, true},
+ {user_interaction, false},
+ {auth_methods, "publickey"},
+ {key_cb, {ssh_agent, [{socket_path, SocketPath}]}}
+ ]),
ssh:close(ConnectionRef),
ssh:stop_daemon(Pid),
ssh_agent_mock_server:stop(SocketPath).
diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_dsa_key b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_dsa_key
new file mode 100644
index 0000000000..51ab6fbd88
--- /dev/null
+++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_dsa_key
@@ -0,0 +1,13 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBuwIBAAKBgQCClaHzE2ul0gKSUxah5W0W8UiJLy4hXngKEqpaUq9SSdVdY2LK
+wVfKH1gt5iuaf1FfzOhsIC9G/GLnjYttXZc92cv/Gfe3gR+s0ni2++MX+T++mE/Q
+diltXv/Hp27PybS67SmiFW7I+RWnT2OKlMPtw2oUuKeztCe5UWjaj/y5FQIVAPLA
+l9RpiU30Z87NRAHY3NTRaqtrAoGANMRxw8UfdtNVR0CrQj3AgPaXOGE4d+G4Gp4X
+skvnCHycSVAjtYxebUkzUzt5Q6f/IabuLUdge3gXrc8BetvrcKbp+XZgM0/Vj2CF
+Ymmy3in6kzGZq7Fw1sZaku6AOU8vLa5woBT2vAcHLLT1bLAzj7viL048T6MfjrOP
+ef8nHvACgYBhDWFQJ1mf99sg92LalVq1dHLmVXb3PTJDfCO/Gz5NFmj9EZbAtdah
+/XcF3DeRF+eEoz48wQF/ExVxSMIhLdL+o+ElpVhlM7Yii+T7dPhkQfEul6zZXu+U
+ykSTXYUbtsfTNRFQGBW2/GfnEc0mnIxfn9v10NEWMzlq5z9wT9P0CgIVAN4wtL5W
+Lv62jKcdskxNyz2NQoBx
+-----END DSA PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_dsa_key.pub b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_dsa_key.pub
new file mode 100644
index 0000000000..4dbb1305b0
--- /dev/null
+++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_dsa_key.pub
@@ -0,0 +1,11 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+AAAAB3NzaC1kc3MAAACBAIKVofMTa6XSApJTFqHlbRbxSIkvLiFeeAoSqlpSr1JJ1V1j
+YsrBV8ofWC3mK5p/UV/M6GwgL0b8YueNi21dlz3Zy/8Z97eBH6zSeLb74xf5P76YT9B2
+KW1e/8enbs/JtLrtKaIVbsj5FadPY4qUw+3DahS4p7O0J7lRaNqP/LkVAAAAFQDywJfU
+aYlN9GfOzUQB2NzU0WqrawAAAIA0xHHDxR9201VHQKtCPcCA9pc4YTh34bganheyS+cI
+fJxJUCO1jF5tSTNTO3lDp/8hpu4tR2B7eBetzwF62+twpun5dmAzT9WPYIViabLeKfqT
+MZmrsXDWxlqS7oA5Ty8trnCgFPa8BwcstPVssDOPu+IvTjxPox+Os495/yce8AAAAIBh
+DWFQJ1mf99sg92LalVq1dHLmVXb3PTJDfCO/Gz5NFmj9EZbAtdah/XcF3DeRF+eEoz48
+wQF/ExVxSMIhLdL+o+ElpVhlM7Yii+T7dPhkQfEul6zZXu+UykSTXYUbtsfTNRFQGBW2
+/GfnEc0mnIxfn9v10NEWMzlq5z9wT9P0Cg==
+---- END SSH2 PUBLIC KEY ----
diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key256 b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key256
new file mode 100644
index 0000000000..2979ea88ed
--- /dev/null
+++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key256
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIMe4MDoit0t8RzSVPwkCBemQ9fhXL+xnTSAWISw8HNCioAoGCCqGSM49
+AwEHoUQDQgAEo2q7U3P6r0W5WGOLtM78UQtofM9UalEhiZeDdiyylsR/RR17Op0s
+VPGSADLmzzgcucLEKy17j2S+oz42VUJy5A==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key256.pub b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key256.pub
new file mode 100644
index 0000000000..85dc419345
--- /dev/null
+++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key256.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKNqu1Nz+q9FuVhji7TO/FELaHzPVGpRIYmXg3YsspbEf0UdezqdLFTxkgAy5s84HLnCxCste49kvqM+NlVCcuQ= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key384 b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key384
new file mode 100644
index 0000000000..fb1a862ded
--- /dev/null
+++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key384
@@ -0,0 +1,6 @@
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDArxbDfh3p1okrD9wQw6jJ4d4DdlBPD5GqXE8bIeRJiK41Sh40LgvPw
+mkqEDSXK++CgBwYFK4EEACKhZANiAAScl43Ih2lWTDKrSox5ve5uiTXil4smsup3
+CfS1XPjKxgBAmlfBim8izbdrT0BFdQzz2joduNMtpt61wO4rGs6jm0UP7Kim9PC7
+Hneb/99fIYopdMH5NMnk60zGO1uZ2vc=
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key384.pub b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key384.pub
new file mode 100644
index 0000000000..428d5fb7d7
--- /dev/null
+++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key384.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBJyXjciHaVZMMqtKjHm97m6JNeKXiyay6ncJ9LVc+MrGAECaV8GKbyLNt2tPQEV1DPPaOh240y2m3rXA7isazqObRQ/sqKb08Lsed5v/318hiil0wfk0yeTrTMY7W5na9w== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key521 b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key521
new file mode 100644
index 0000000000..3e51ec2ecd
--- /dev/null
+++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key521
@@ -0,0 +1,7 @@
+-----BEGIN EC PRIVATE KEY-----
+MIHcAgEBBEIB8O1BFkl2HQjQLRLonEZ97da/h39DMa9/0/hvPZWAI8gUPEQcHxRx
+U7b09p3Zh+EBbMFq8+1ae9ds+ZTxE4WFSvKgBwYFK4EEACOhgYkDgYYABAAlWVjq
+Bzg7Wt4gE6UNb1lRE2cnlmH2L/A5uo6qZRx5lPnSKOxEhxSb/Oay1+9d6KRdrh6/
+vlhd9SHDBhLcAPDvWgBnJIEj92Q3pXX4JtoitL0yl+SvvU+vUh966mzHShHzj8p5
+ccOgPkPNoA70yrpGzkIhPezpZOQdCaOXj/jFqNCTDg==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key521.pub b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key521.pub
new file mode 100644
index 0000000000..017a29f4da
--- /dev/null
+++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key521.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAAlWVjqBzg7Wt4gE6UNb1lRE2cnlmH2L/A5uo6qZRx5lPnSKOxEhxSb/Oay1+9d6KRdrh6/vlhd9SHDBhLcAPDvWgBnJIEj92Q3pXX4JtoitL0yl+SvvU+vUh966mzHShHzj8p5ccOgPkPNoA70yrpGzkIhPezpZOQdCaOXj/jFqNCTDg== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed25519_key b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed25519_key
new file mode 100644
index 0000000000..13a8fcf491
--- /dev/null
+++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed25519_key
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQAAAJi+h4O7voeD
+uwAAAAtzc2gtZWQyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQ
+AAAEBaOcJfGPNemKc1wPHTCmM4Kwvh6dZ0CqY14UT361UnN0lI66JgZZo72XJ7wGBp+h3W
+TDw/pxXddvaomAIHrIl9AAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed25519_key.pub b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed25519_key.pub
new file mode 100644
index 0000000000..156ef4045c
--- /dev/null
+++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed25519_key.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIElI66JgZZo72XJ7wGBp+h3WTDw/pxXddvaomAIHrIl9 uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed448_key b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed448_key
new file mode 100644
index 0000000000..31a7e4e8c3
--- /dev/null
+++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed448_key
@@ -0,0 +1,10 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz
+c2gtZWQ0NDgAAAA5X9dEm1m0Yf0s54fsYWrUah2hNCSFpw4fig6nXYDpZ3jt8SR2
+m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA
+ADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHl
+D2zR+hq+r+glYYAAAABybIKlYsuAjRDWMr6JyFE+v2ySnzTd+oyfY8mWDvbjSKNS
+jIo/zC8ETjmj/FuUSS+PAy51SaIAmPlbX9dEm1m0Yf0s54fsYWrUah2hNCSFpw4f
+ig6nXYDpZ3jt8SR2m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAAAAECAwQ=
+-----END OPENSSH PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed448_key.pub b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed448_key.pub
new file mode 100644
index 0000000000..8c390dcb58
--- /dev/null
+++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed448_key.pub
@@ -0,0 +1 @@
+ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA=
diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_rsa_key.pub b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_rsa_key.pub
new file mode 100644
index 0000000000..75d2025c71
--- /dev/null
+++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_rsa_key.pub
@@ -0,0 +1,5 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+AAAAB3NzaC1yc2EAAAADAQABAAAAgQDCZX+4FBDwZIh9y/Uxee1VJnEXlowpz2yDKwj8
+semM4q843337zbNfxHmladB1lpz2NqyxI175xMIJuDxogyZdsOxGnFAzAnthR4dqL/RW
+RWzjaxSB6IAO9SPYVVlrpZ+1hsjLW79fwXK/yc8VdhRuWTeQiRgYY2ek8+OKbOqz4Q==
+---- END SSH2 PUBLIC KEY ----
diff --git a/lib/ssh/test/ssh_algorithms_SUITE.erl b/lib/ssh/test/ssh_algorithms_SUITE.erl
index 06996d7e48..4832c0ad3b 100644
--- a/lib/ssh/test/ssh_algorithms_SUITE.erl
+++ b/lib/ssh/test/ssh_algorithms_SUITE.erl
@@ -35,7 +35,7 @@
suite() ->
[{ct_hooks,[ts_install_cth]},
- {timetrap,{seconds,60}}].
+ {timetrap,{seconds,120}}].
all() ->
%% [{group,kex},{group,cipher}... etc
@@ -48,9 +48,9 @@ groups() ->
SshdAlgos = extract_algos(ssh_test_lib:default_algorithms(sshd)),
DoubleAlgos =
- [{Tag, double(Algs)} || {Tag,Algs} <- ErlAlgos,
- length(Algs) > 1,
- lists:member(Tag, two_way_tags())],
+ [{Tag, double(Tag,Algs)} || {Tag,Algs} <- ErlAlgos,
+ length(Algs) > 1,
+ lists:member(Tag, two_way_tags())],
TagGroupSet =
[{Tag, [], group_members_for_tag(Tag,Algs,DoubleAlgos)}
|| {Tag,Algs} <- ErlAlgos,
@@ -60,14 +60,14 @@ groups() ->
TypeSSH = ssh_test_lib:ssh_type(),
AlgoTcSet =
- [{Alg, [parallel], specific_test_cases(Tag,Alg,SshcAlgos,SshdAlgos,TypeSSH)}
+ [{Alg, [], specific_test_cases(Tag,Alg,SshcAlgos,SshdAlgos,TypeSSH)}
|| {Tag,Algs} <- ErlAlgos ++ DoubleAlgos,
Alg <- Algs],
TagGroupSet ++ AlgoTcSet.
tags() -> [kex,cipher,mac,compression,public_key].
-two_way_tags() -> [cipher,mac,compression].
+two_way_tags() -> [cipher,mac,compression, public_key].
%%--------------------------------------------------------------------
init_per_suite(Config) ->
@@ -91,7 +91,10 @@ init_per_suite(Config) ->
ssh_test_lib:installed_ssh_version("TIMEOUT"),
ssh:default_algorithms(),
crypto:info_lib(),
- ssh_test_lib:default_algorithms(sshc),
+ ssh_test_lib:default_algorithms(sshc,
+ %% Use a fake system_dir to enable the test
+ %% daemon to start:
+ [{system_dir,proplists:get_value(data_dir,Config)}]),
ssh_test_lib:default_algorithms(sshd),
{?DEFAULT_DH_GROUP_MIN,?DEFAULT_DH_GROUP_NBITS,?DEFAULT_DH_GROUP_MAX},
public_key:dh_gex_group_sizes(),
@@ -126,22 +129,31 @@ init_per_group(Group, Config) ->
init_per_group(public_key=Tag, Alg, Config) ->
+ PA =
+ case split(Tag, Alg) of
+ [_] ->
+ [Alg];
+ [A1,A2] ->
+ [A1,A2]
+ end,
OtherAlgs = [{T,L} || {T,L} <- ssh_transport:supported_algorithms(), T=/=Tag],
- ct:log("Init tests for public_key ~p~nOtherAlgs=~p",[Alg,OtherAlgs]),
- PrefAlgs = {preferred_algorithms,[{Tag,[Alg]}|OtherAlgs]},
+ ct:log("Init tests for public_key ~p~nOtherAlgs=~p",[PA,OtherAlgs]),
+ PrefAlgs = {preferred_algorithms,[{Tag,PA}|OtherAlgs]},
%% Daemon started later in init_per_testcase
try
- setup_pubkey(Alg,
+ setup_pubkey(PA,
[{pref_algs,PrefAlgs},
- {tag_alg,{Tag,Alg}}
+ {tag_alg,{Tag,PA}}
| Config])
catch
- _:_ -> {skip, io_lib:format("Unsupported: ~p",[Alg])}
+ _C:_E:_S ->
+ ct:log("Exception ~p:~p~n~p",[_C,_E,_S]),
+ {skip, io_lib:format("Unsupported: ~p",[Alg])}
end;
init_per_group(Tag, Alg, Config) ->
PA =
- case split(Alg) of
+ case split(Tag, Alg) of
[_] ->
[Alg];
[A1,A2] ->
@@ -153,7 +165,7 @@ init_per_group(Tag, Alg, Config) ->
PrefAlgs = {preferred_algorithms,[{Tag,PA}|OtherAlgs]},
start_std_daemon([PrefAlgs],
[{pref_algs,PrefAlgs},
- {tag_alg,{Tag,Alg}}
+ {tag_alg,{Tag,[Alg]}}
| Config]).
@@ -173,6 +185,7 @@ init_per_testcase(TC, Config) ->
init_per_testcase(TC, {public_key,Alg}, Config) ->
+ ct:log("init_per_testcase TC=~p, Alg=~p",[TC,Alg]),
ExtraOpts = case TC of
simple_connect ->
[{user_dir, proplists:get_value(priv_dir,Config)}];
@@ -180,21 +193,26 @@ init_per_testcase(TC, {public_key,Alg}, Config) ->
[]
end,
Opts = pubkey_opts(Config) ++ ExtraOpts,
- case {ssh_file:user_key(Alg,Opts), ssh_file:host_key(Alg,Opts)} of
+ {UserAlg,SrvrAlg} =
+ case Alg of
+ [A1,A2] -> {A1,A2};
+ [A0] -> {A0,A0}
+ end,
+ case {ssh_file:user_key(UserAlg,Opts), ssh_file:host_key(SrvrAlg,Opts)} of
{{ok,_}, {ok,_}} ->
start_pubkey_daemon([proplists:get_value(pref_algs,Config)
| ExtraOpts],
[{extra_daemon,true}|Config]);
{{ok,_}, {error,Err}} ->
- ct:log("Alg = ~p~nOpts = ~p",[Alg,Opts]),
+ ct:log("SrvrAlg = ~p~nOpts = ~p",[SrvrAlg,Opts]),
{skip, io_lib:format("No host key: ~p",[Err])};
{{error,Err}, {ok,_}} ->
- ct:log("Alg = ~p~nOpts = ~p",[Alg,Opts]),
+ ct:log("UserAlg = ~p~nOpts = ~p",[UserAlg,Opts]),
{skip, io_lib:format("No user key: ~p",[Err])};
_ ->
- ct:log("Alg = ~p~nOpts = ~p",[Alg,Opts]),
+ ct:log("UserAlg = ~p SrvrAlg = ~p~nOpts = ~p",[UserAlg,SrvrAlg,Opts]),
{skip, "Neither host nor user key"}
end;
@@ -244,17 +262,22 @@ simple_exec(Config) ->
%%--------------------------------------------------------------------
%% A simple exec call
simple_connect(Config) ->
+ ct:log("PrivDir ~p:~n~p~n~nPrivDir/system: ~p",[proplists:get_value(priv_dir,Config),
+ file:list_dir(proplists:get_value(priv_dir,Config)),
+ catch file:list_dir(
+ filename:join(proplists:get_value(priv_dir,Config),
+ system))]),
{Host,Port} = proplists:get_value(srvr_addr, Config),
{preferred_algorithms,AlgEntries} = proplists:get_value(pref_algs, Config),
Opts =
case proplists:get_value(tag_alg, Config) of
- {public_key,Alg} -> [{pref_public_key_algs,[Alg]},
+ {public_key,Alg} -> [{pref_public_key_algs,Alg},
{preferred_algorithms,AlgEntries}];
_ -> [{modify_algorithms,[{append,AlgEntries}]}]
end,
ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port,
[{silently_accept_hosts, true},
- {user_interaction, false} |
+ {user_interaction, false} |
Opts]),
ct:log("~p:~p connected! ~p",[?MODULE,?LINE,ConnectionRef]),
ssh:close(ConnectionRef).
@@ -280,7 +303,7 @@ try_exec_simple_group(Group, Config) ->
%% Testing all default groups
simple_exec_groups() ->
- [{timetrap,{seconds,180}}].
+ [{timetrap,{seconds,240}}].
simple_exec_groups(Config) ->
Sizes = interpolate( public_key:dh_gex_group_sizes() ),
@@ -317,7 +340,10 @@ sshc_simple_exec_os_cmd(Config) ->
Result = ssh_test_lib:open_sshc(Host, Port,
[" -C"
" -o UserKnownHostsFile=",KnownHosts,
+ " -o CheckHostIP=no"
" -o StrictHostKeyChecking=no"
+ " -q"
+ " -x"
],
" 1+1."),
Parent ! {result, self(), Result, "2"}
@@ -342,7 +368,7 @@ sshc_simple_exec_os_cmd(Config) ->
sshd_simple_exec(Config) ->
ClientPubKeyOpts =
case proplists:get_value(tag_alg,Config) of
- {public_key,Alg} -> [{pref_public_key_algs,[Alg]}];
+ {public_key,Alg} -> [{pref_public_key_algs,Alg}];
_ -> []
end,
ConnectionRef = ssh_test_lib:connect(22, [{silently_accept_hosts, true},
@@ -395,13 +421,20 @@ sshd_simple_exec(Config) ->
group_members_for_tag(Tag, Algos, DoubleAlgos) ->
[{group,Alg} || Alg <- Algos++proplists:get_value(Tag,DoubleAlgos,[])].
-double(Algs) -> [concat(A1,A2) || A1 <- Algs,
- A2 <- Algs,
- A1 =/= A2].
+double(Tag, Algs) -> [concat(Tag,A1,A2) || A1 <- Algs,
+ A2 <- Algs,
+ A1 =/= A2].
-concat(A1, A2) -> list_to_atom(lists:concat([A1," + ",A2])).
+concat(Tag, A1, A2) ->
+ list_to_atom(lists:concat(["D: ",Tag," ",A1," + ",A2])).
-split(Alg) -> ssh_test_lib:to_atoms(string:tokens(atom_to_list(Alg), " + ")).
+split(TagA, Alg) ->
+ Tag = atom_to_list(TagA),
+ ssh_test_lib:to_atoms(
+ case string:tokens(atom_to_list(Alg), " ") of
+ ["D:",Tag,A1,"+",A2] ->[A1,A2];
+ Other -> Other
+ end).
specific_test_cases(Tag, Alg, SshcAlgos, SshdAlgos, TypeSSH) ->
case Tag of
@@ -435,7 +468,7 @@ supports(Tag, Alg, Algos) ->
lists:all(fun(A) ->
lists:member(A, proplists:get_value(Tag, Algos,[]))
end,
- split(Alg)).
+ split(Tag, Alg)).
extract_algos(Spec) ->
@@ -475,21 +508,33 @@ pubkey_opts(Config) ->
{system_dir, SystemDir}].
-setup_pubkey(Alg, Config) ->
+setup_pubkey([AlgClient, AlgServer], Config) ->
DataDir = proplists:get_value(data_dir, Config),
UserDir = proplists:get_value(priv_dir, Config),
+ ssh_test_lib:del_dir_contents(UserDir),
+ ok = ssh_test_lib:setup_user_key(AlgClient, DataDir, UserDir),
+ _SysDir = ssh_test_lib:setup_host_key_create_dir(AlgServer, DataDir, UserDir),
+try ct:log("~p:~p AlgClient=~p, AlgServer=~p~nPrivDir ~p:~n~p~n~nSYsDir=~p~nPrivDir/system: ~p",
+ [?MODULE,?LINE,
+ AlgClient, AlgServer,
+ proplists:get_value(priv_dir,Config),
+ file:list_dir(proplists:get_value(priv_dir,Config)),
+ _SysDir,
+ catch file:list_dir(
+ filename:join(proplists:get_value(priv_dir,Config),
+ system))
+ ])
+catch _C:_E:_S ->
+ ct:log("~p:~p ~p:~p~n~p",[?MODULE,?LINE,_C,_E,_S])
+end,
+ Config;
+
+setup_pubkey([Alg], Config) ->
+ DataDir = proplists:get_value(data_dir, Config),
+ PrivDir = proplists:get_value(priv_dir, Config),
ct:log("Setup keys for ~p",[Alg]),
- case Alg of
- 'ssh-dss' -> ssh_test_lib:setup_dsa(DataDir, UserDir);
- 'ssh-rsa' -> ssh_test_lib:setup_rsa(DataDir, UserDir);
- 'rsa-sha2-256' -> ssh_test_lib:setup_rsa(DataDir, UserDir);
- 'rsa-sha2-512' -> ssh_test_lib:setup_rsa(DataDir, UserDir);
- 'ecdsa-sha2-nistp256' -> ssh_test_lib:setup_ecdsa("256", DataDir, UserDir);
- 'ecdsa-sha2-nistp384' -> ssh_test_lib:setup_ecdsa("384", DataDir, UserDir);
- 'ecdsa-sha2-nistp521' -> ssh_test_lib:setup_ecdsa("521", DataDir, UserDir);
- 'ssh-ed25519' -> ssh_test_lib:setup_eddsa(ed25519, DataDir, UserDir);
- 'ssh-ed448' -> ssh_test_lib:setup_eddsa(ed448, DataDir, UserDir)
- end,
+ ssh_test_lib:setup_user_key(Alg, DataDir, PrivDir),
+ ssh_test_lib:setup_host_key_create_dir(Alg, DataDir, PrivDir),
Config.
diff --git a/lib/ssh/test/ssh_algorithms_SUITE_data/id_dsa b/lib/ssh/test/ssh_algorithms_SUITE_data/id_dsa
index 273866b2a6..24628e071b 100644
--- a/lib/ssh/test/ssh_algorithms_SUITE_data/id_dsa
+++ b/lib/ssh/test/ssh_algorithms_SUITE_data/id_dsa
@@ -1,12 +1,12 @@
-----BEGIN DSA PRIVATE KEY-----
-MIIBvAIBAAKBgQDIaRzrxz7bSJXh6z+w1lfTW7sfYNMDNXsfN/nvt6Rqi95K4Q+8
-Xpa+LJ6oZCxfMto8w2fZ6uzikRCgBbvzos6L+GPKWhuaBTT5gYsEFphowWN9uPQy
-BHwRApOkpZNObN+SnMRuO7lYpwMLnYJdLJTCOxam3mfAzTqdCnpgAWAidwIVANFI
-VPGuBxMc84proP+r8X+8hT8RAoGBAI9A+KYH0LLFFIaxgDBA7xnnHIECQrhgiNIN
-QfvFPH1Drf5He50OeXFEvR4gt7f3qScWxLUKrpBysS1huJHiJCOq+iREyfoqjtfc
-zZT01XejDvX5ck+rz/kiWQk9cZwW6lqLtn3GxIcQvOnrzskT7YyFJ7fx3U98PxmY
-qZrXhJEjAoGABFZL6Ztdx9A+7qd5s2A4T7W9fztsm85CxM4MuRuzR2U6zU/Z4D6U
-OXiS9UZsz7yIQQfqrc5IgPmSa5og9oflQnQSQcRrY1XvpYLft1DOkH6+wdFf0OlG
-hIKihHN+jvgbcEmJTHJwsiW2HJCH6qXK33s4lmthEeIqXuZPduaBnGICFQDKLb+y
-afjRTP7FVHq+SBQUy8iqSQ==
+MIIBuwIBAAKBgQDIywHurUpOq6kZuMn+XlRzR4hAxF6qwSkuEqkV7iHnLQ0kIwf3
+uAmjFDhuEsQ8653SLxGVvTNp+KFFgDXiLqgM7TPUwDnpbvzEZHPAU+/zPt4sdY2D
+txBfJwT2SFlK6HPOxOcxdDuD+/a59sh8hk/YVOU7ZTcBVsVG8Got4UcF5QIVAPGd
+CPDQKSTlPiM9OwBB1+9p11k5AoGARLxw4l17mET9cU0uf4Ppe5nsCbODJv44ZrSs
+picvypGVLrLcN5KWbm3vjRFCQ5LFunAG3FwLC2Sh0CH6TemoIfRPsRHR7wvpBGdr
+c693UlMOis/mcmvNMQAzuQNW9WrxdzsvWR/r5s6NEHWqKUJGXSPi2d+Ijq/mCOmI
+hzLzyiACgYEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4
+cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+C
+ROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNgCFEjA7wTC
+sQCY/I35vb6GUJn9tEdP
-----END DSA PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_algorithms_SUITE_data/id_dsa.pub b/lib/ssh/test/ssh_algorithms_SUITE_data/id_dsa.pub
new file mode 100644
index 0000000000..018ef6f537
--- /dev/null
+++ b/lib/ssh/test/ssh_algorithms_SUITE_data/id_dsa.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAMjLAe6tSk6rqRm4yf5eVHNHiEDEXqrBKS4SqRXuIectDSQjB/e4CaMUOG4SxDzrndIvEZW9M2n4oUWANeIuqAztM9TAOelu/MRkc8BT7/M+3ix1jYO3EF8nBPZIWUroc87E5zF0O4P79rn2yHyGT9hU5TtlNwFWxUbwai3hRwXlAAAAFQDxnQjw0Ckk5T4jPTsAQdfvaddZOQAAAIBEvHDiXXuYRP1xTS5/g+l7mewJs4Mm/jhmtKymJy/KkZUustw3kpZube+NEUJDksW6cAbcXAsLZKHQIfpN6agh9E+xEdHvC+kEZ2tzr3dSUw6Kz+Zya80xADO5A1b1avF3Oy9ZH+vmzo0QdaopQkZdI+LZ34iOr+YI6YiHMvPKIAAAAIEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+CROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNg= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_algorithms_SUITE_data/id_rsa b/lib/ssh/test/ssh_algorithms_SUITE_data/id_rsa
index 1f8577e866..2202c2ead8 100644
--- a/lib/ssh/test/ssh_algorithms_SUITE_data/id_rsa
+++ b/lib/ssh/test/ssh_algorithms_SUITE_data/id_rsa
@@ -1,27 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
-MIIEpAIBAAKCAQEAwT3nTd9tkKLVaqVcSpK04ID1VVCSCEOW0KXLienX4LD/q7lo
-aOyuc044hDnv3P+nz2SNSR05N5dAFaKjCKw7DWz30HgrSbAFxW+KUHAG55brZMOF
-oevJOlag8ih1AXjgpEcsJtGILshXyJKXA02IkeH62xeb/VUFgw1iRbbLblhcf2Mb
-lyiTcWQZRei57cAA+IjjrxjGJxvG2eLyH1zy+BJa/1Lo4Kfk9tN0rfH1RBzJ5hjK
-teQQa4dfc0WNRTdPM2Lzpab8DJw0C2EqO3lUu1JqOHkbDoLwEyX6mB3jRz9Kkh3Q
-oSQG89+G7eePu+jl63qIhdLEUbC+JZlDlvlqEQIDAQABAoIBADmtaWGT45b9Eyge
-rRpRCY3Mz+0j/EJdMiGaqtLCKj4VdmpiD6jpo/Qkj7fftxlGcWb0gzskbtSJ34XV
-okXPalzKfnkJtRnsYPyaGzWBCn6LTD0qIrO+tbQk8Sr2Kl5DHwHJgIMhnT0hbRof
-rtU8ihvI0GAeft+xRdDk6MUYF0YasqDDoiNTKFLTFu3bz7Jbh42zlmmu+u4PRO5b
-AgxsBxvo9DdXC2lKTB/SKv1kWL2bysr6La3WBKmUwIuOEfLDuT3Vboy9orIDFog8
-QOmEgAZeR8PVw9MhkaHdosWJqL4G0kqIvlhf6rT5ZDUeKGBo2qmO48jHBCGJBFGw
-FKJNoNUCgYEA6tjUiKCILKmVtkD7tJtQqEMs/NRDDX6/5uCfROqIMM6ABM3RqgRv
-bMJffyGjXG+M86DxDgX0i25U8I0wmNpOU9P6qCgvhaOH+h+TNC37B6IZ+VwGxgDZ
-LbdTTxA5WaKzDmwzlUHgAlf31jQ5j3Bf9Js7Tyz/X5fn3fd8i6Mwu6MCgYEA0qW6
-oIkRtzO0jkX5FHmQGuhHmhguzF3WXy/TcrAFQL5EsDQofbAUv70DxxFfufsfgPUP
-wKpj/VhED2JFnqVV/68nArWl2eeLSX2gKrgHMy/9AoVYC1w/O2JPilbZXt1W0liT
-SB4ZzCONom1m/wtaGjStjup2pSxcm05DKixD3rsCgYEAsKFygGwU31qRAmmvpm/m
-YxdbH7FZ2S2KkdBBmei3k9XMTVCrr670Sx2KC6k2H9C6d4aFpuFtwuyxr9bRRTV0
-EfJuJMlMrLuJCuNyqJ0on94YoQbJBWUf8xVd8CooqDUJbQCOb2UDYV/eRFo1LJ/9
-W5DhM7SJQdGTj8uS/cc4YPcCgYEAkQ5DKA17v5bBfT++OFVF4OGXfQuuHll4J/A9
-Qbrowx7DGjuwrmy0vRyiH1FdhCrkFN+sy1YKqQlBRP69RnRAdmPdD0abQSTri94Q
-j5pOivc+2Z+Nc7VAbdpTP8ZyxZrSEOOh+IWR6juJaxK/XF4q2+Tup33Z2gBkfSY1
-pjL5QcUCgYBZ1FQwPD1Z3EPXhqs7yVcOGp286BffEnd3xJyvqx13Jsd/LzmG8YzR
-FLQ9XLRehs+exdt6yPEMcIkwWKiGPkJ4OXce9arbFp9ILb0Ppo5y41oBsrFV2BCv
-NgrB5gTKtN/ipRWwND5zm+yHE3FJR8bgniTVtFF8z2XSWlWduE5gRA==
+MIIEpAIBAAKCAQEAztjiyj2tdfkji0fewWS0kABg0IABgG20NvL1PnHJLr98we7w
+W7f3j27EGjW/ApuycsWXXKi0L82q8uDicoHHb3JI2JkT70oi0yG1Dx/zwPN+dkA7
+LBT1J3UK2hJTFPhp855CwY/ss9xpBsd1Fv3zuHifEqNGljeg1PjmQ3pNhxA/M0aZ
+cLnfIUdZ5Hr+t+4es3zaWo4tLBKmZu6BkVGQKPGXeMkIAMtJlG24l7qKDRkR5TYA
+ZT7P8Vn7hnuFuCNbrJSm686GawBxTQXom23dg9UcWxoHB7UiHFoR6j0bQAX+4R7b
+IwculRDcvzrgCu6u06oFILwY7MlsxpX9hGTl1wIDAQABAoIBAFeP6pmQeICrYceR
+OhQGLIWVE2bP+VLDnflw6i5v/qlieE6kdm1tOEgorK0nuV9CR81cJdIcvIJL/yTn
+3BR7KdDcwUenrY+rg4h7CWmIrigtK4ilciccDBeS7XAZN8B11GxDv6Cu65XMJU2w
+W7nK8URTE4vRQI1QqS3e26MPAAi/LVOt3ZPI6zg/GHEwnq0IVSQAOndLBr/IWZk5
+SANrkfwX8WS7/UxZgDptT9dyUQ5Pnj5mieTlIvBwyczdhZ7RDa8HdCSHW3xF83V1
+A0pkn6+TRojumYyr4RrPQj6htE64Hgx9w1Dv/UINjPXl5mGlbxQHMWGzlqD/qpyI
+wg7RakECgYEA+9ARZpHfEFz+EEFi8l9J+BtJDo00WaKCOZHh5UJ8W+NreqSd8nSx
+5u6wYwMJjRX2Hwv+FBEhxGbo1+ff6p++cYmiSlDtN2XRCDkBWvvGlxu55BDULrhx
+f8lqaV3XGmOy2rQusp8hiHmkmPJCSVj3oJqQnbqJ2zahXAx1rTPwHqECgYEA0kln
+4h+ZkZ+aldOMGF0d0txTcTqZvsSVKiFTSD9of/fiSDqb6xtLT2+ys6FZoFL9lyK8
+gtqH642CDQ+3WT6Nmn4kMF5HNVpEuCeRDeRhiquWeKaAQDyvZ5ym1+Cn3GhsO7Di
+d2LJKV5hOoN77loVY5nwnUVIJ0h+WLf0T7DTCXcCgYEAiNT7X50MdTvS4splFgcp
+jqRlAn9AXySrVtUqxwVlxhjCIpapLUK0GSTCvEq+OeghIaXGnujgTHUPOaNKTZgY
+SGHdyjxHar7s42b2kZYWx63NSVzLr8eSBTpRlIflhvV+DtGyPmWyNxLCmkmqM2kg
+xii3RL5EgtYgwIAUwdVjOYECgYBRPlsMWfkS8f7fc+PkZdVn6gey71kHAxw+MrHi
+b90H09Vw4nPq2Zi3EAiSrfvanTWsdpcuVw+8See89B16NViwH5wLs+D/E+kI3QCF
+xX6J/NEdu/ZA2zFJbpRnQzyXQyDNzwEv7tKZUQVvfe0boWIyIP99Q48k3jUyQZ/6
+Se6+8QKBgQCXl8H2K3CsZxoujKLb2qoEOPbxJQ2hxoMTS5XuQECReIVsNuptWrur
+DF8WJi/B6AqwRX1P3l56RNwqB1yDBqv0QVLpU7vU/FmWqLWTn0r3AvM74qftvfAE
+oa31wcYoCqPJoKgCG7TThLhNt2v5hL7sVgZNO0ueAiHhJbFLaf7ceg==
-----END RSA PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_algorithms_SUITE_data/id_rsa.pub b/lib/ssh/test/ssh_algorithms_SUITE_data/id_rsa.pub
new file mode 100644
index 0000000000..b4084d320a
--- /dev/null
+++ b/lib/ssh/test/ssh_algorithms_SUITE_data/id_rsa.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDO2OLKPa11+SOLR97BZLSQAGDQgAGAbbQ28vU+cckuv3zB7vBbt/ePbsQaNb8Cm7JyxZdcqLQvzary4OJygcdvckjYmRPvSiLTIbUPH/PA8352QDssFPUndQraElMU+GnznkLBj+yz3GkGx3UW/fO4eJ8So0aWN6DU+OZDek2HED8zRplwud8hR1nkev637h6zfNpaji0sEqZm7oGRUZAo8Zd4yQgAy0mUbbiXuooNGRHlNgBlPs/xWfuGe4W4I1uslKbrzoZrAHFNBeibbd2D1RxbGgcHtSIcWhHqPRtABf7hHtsjBy6VENy/OuAK7q7TqgUgvBjsyWzGlf2EZOXX uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl
index ecec805696..1269ab393e 100644
--- a/lib/ssh/test/ssh_basic_SUITE.erl
+++ b/lib/ssh/test/ssh_basic_SUITE.erl
@@ -40,103 +40,64 @@
suite() ->
[{ct_hooks,[ts_install_cth]},
- {timetrap,{seconds,40}}].
+ {timetrap,{seconds,90}}].
all() ->
- [{group, all_tests},
- daemon_already_started
+ [{group, all_tests}
].
%%%-define(PARALLEL, ).
-define(PARALLEL, parallel).
groups() ->
- [{all_tests, [?PARALLEL], [{group, ssh_renegotiate_SUITE},
- {group, ssh_basic_SUITE},
- ssh_file_is_host_key,
- ssh_file_is_host_key_misc,
- ssh_file_is_auth_key
- ]},
- {ssh_basic_SUITE, [], [app_test,
- appup_test,
- {group, dsa_key},
- {group, rsa_key},
- {group, ecdsa_sha2_nistp256_key},
- {group, ecdsa_sha2_nistp384_key},
- {group, ecdsa_sha2_nistp521_key},
- {group, ed25519_key},
- {group, ed448_key},
- {group, dsa_pass_key},
- {group, rsa_pass_key},
- {group, ecdsa_sha2_nistp256_pass_key},
- {group, ecdsa_sha2_nistp384_pass_key},
- {group, ecdsa_sha2_nistp521_pass_key},
- {group, host_user_key_differs},
- {group, key_cb},
- {group, internal_error},
- {group, rsa_host_key_is_actualy_ecdsa},
- daemon_already_started,
- double_close,
- daemon_opt_fd,
- multi_daemon_opt_fd,
- packet_size,
- ssh_info_print,
- {group, login_bad_pwd_no_retry},
- shell_exit_status,
- setopts_getopts
- ]},
-
- {ssh_renegotiate_SUITE, [?PARALLEL], [rekey0,
- rekey1,
- rekey2,
- rekey3,
- rekey4,
- rekey_limit_client,
- rekey_limit_daemon,
- rekey_time_limit_client,
- rekey_time_limit_daemon,
- norekey_limit_client,
- norekey_limit_daemon,
- renegotiate1,
- renegotiate2]},
-
- {dsa_key, [], [{group, basic}]},
- {rsa_key, [], [{group, basic}]},
- {ecdsa_sha2_nistp256_key, [], [{group, basic}]},
- {ecdsa_sha2_nistp384_key, [], [{group, basic}]},
- {ecdsa_sha2_nistp521_key, [], [{group, basic}]},
- {ed25519_key, [], [{group, basic}]},
- {ed448_key, [], [{group, basic}]},
- {rsa_host_key_is_actualy_ecdsa, [], [fail_daemon_start]},
- {host_user_key_differs, [?PARALLEL], [exec_key_differs1,
- exec_key_differs2,
- exec_key_differs3,
- exec_key_differs_fail]},
- {dsa_pass_key, [], [pass_phrase]},
- {rsa_pass_key, [], [pass_phrase]},
- {ecdsa_sha2_nistp256_pass_key, [], [pass_phrase]},
- {ecdsa_sha2_nistp384_pass_key, [], [pass_phrase]},
- {ecdsa_sha2_nistp521_pass_key, [], [pass_phrase]},
+ [{all_tests, [?PARALLEL], [{group, sequential},
+ {group, p_basic},
+ {group, internal_error},
+ {group, login_bad_pwd_no_retry},
+ {group, key_cb}
+ ]},
+
+ {sequential, [], [app_test,
+ appup_test,
+ daemon_already_started,
+ daemon_error_closes_port, % Should be re-written..
+ double_close,
+ daemon_opt_fd,
+ multi_daemon_opt_fd,
+ packet_size,
+ ssh_info_print,
+ shell_exit_status,
+ setopts_getopts,
+ known_hosts,
+ ssh_file_is_host_key,
+ ssh_file_is_host_key_misc,
+ ssh_file_is_auth_key
+ ]},
+
{key_cb, [?PARALLEL], [key_callback, key_callback_options]},
- {internal_error, [], [internal_error]},
+
+ {internal_error, [?PARALLEL], [internal_error]},
+
{login_bad_pwd_no_retry, [?PARALLEL], [login_bad_pwd_no_retry1,
- login_bad_pwd_no_retry2,
- login_bad_pwd_no_retry3,
- login_bad_pwd_no_retry4,
- login_bad_pwd_no_retry5
- ]},
+ login_bad_pwd_no_retry2,
+ login_bad_pwd_no_retry3,
+ login_bad_pwd_no_retry4,
+ login_bad_pwd_no_retry5
+ ]},
- {basic, [], [{group,p_basic},
- shell, shell_no_unicode, shell_unicode_string,
- close,
- known_hosts
- ]},
{p_basic, [?PARALLEL], [send, peername_sockname,
- exec, exec_compressed,
- exec_with_io_out, exec_with_io_in,
- cli,
- idle_time_client, idle_time_server, openssh_zlib_basic_test,
- misc_ssh_options, inet_option, inet6_option]}
+ exec, exec_compressed,
+ exec_with_io_out, exec_with_io_in,
+ cli,
+ idle_time_client, idle_time_server, openssh_zlib_basic_test,
+ misc_ssh_options, inet_option, inet6_option
+
+ ,shell,
+ shell_no_unicode,
+ shell_unicode_string,
+ close
+
+ ]}
].
@@ -147,6 +108,8 @@ groups() ->
init_per_suite(Config) ->
?CHECK_CRYPTO(begin
ssh:start(),
+ ct:log("Pub keys setup for: ~p",
+ [ssh_test_lib:setup_all_user_host_keys(Config)]),
Config
end).
@@ -154,282 +117,22 @@ end_per_suite(_Config) ->
ssh:stop().
%%--------------------------------------------------------------------
-init_per_group(ssh_renegotiate_SUITE, Config) ->
- [{preferred_algorithms, ssh:default_algorithms()} | Config];
-init_per_group(dsa_key, Config) ->
- case lists:member('ssh-dss',
- ssh_transport:default_algorithms(public_key)) of
- true ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_dsa(DataDir, PrivDir),
- Config;
- false ->
- {skip, unsupported_pub_key}
- end;
-init_per_group(rsa_key, Config) ->
- case lists:member('ssh-rsa',
- ssh_transport:default_algorithms(public_key)) of
- true ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_rsa(DataDir, PrivDir),
- Config;
- false ->
- {skip, unsupported_pub_key}
- end;
-init_per_group(rsa_host_key_is_actualy_ecdsa, Config) ->
- case
- lists:member('ssh-rsa',
- ssh_transport:default_algorithms(public_key)) and
- lists:member('ecdsa-sha2-nistp256',
- ssh_transport:default_algorithms(public_key))
- of
- true ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_ecdsa("256", DataDir, PrivDir),
- %% The following sets up bad rsa keys:
- begin
- UserDir = PrivDir,
- System = filename:join(UserDir, "system"),
- file:copy(filename:join(DataDir, "id_rsa"), filename:join(UserDir, "id_rsa")),
- file:rename(filename:join(System, "ssh_host_ecdsa_key"), filename:join(System, "ssh_host_rsa_key")),
- file:rename(filename:join(System, "ssh_host_ecdsa_key.pub"), filename:join(System, "ssh_host_rsa_key.pub")),
- ssh_test_lib:setup_rsa_known_host(DataDir, UserDir),
- ssh_test_lib:setup_rsa_auth_keys(DataDir, UserDir)
- end,
- Config;
- false ->
- {skip, unsupported_pub_key}
- end;
-init_per_group(ecdsa_sha2_nistp256_key, Config) ->
- case lists:member('ecdsa-sha2-nistp256',
- ssh_transport:default_algorithms(public_key)) of
- true ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_ecdsa("256", DataDir, PrivDir),
- Config;
- false ->
- {skip, unsupported_pub_key}
- end;
-init_per_group(ecdsa_sha2_nistp384_key, Config) ->
- case lists:member('ecdsa-sha2-nistp384',
- ssh_transport:default_algorithms(public_key)) of
- true ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_ecdsa("384", DataDir, PrivDir),
- Config;
- false ->
- {skip, unsupported_pub_key}
- end;
-init_per_group(ecdsa_sha2_nistp521_key, Config) ->
- case lists:member('ecdsa-sha2-nistp521',
- ssh_transport:default_algorithms(public_key)) of
- true ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_ecdsa("521", DataDir, PrivDir),
- Config;
- false ->
- {skip, unsupported_pub_key}
- end;
-init_per_group(ed25519_key, Config) ->
- case lists:member('ssh-ed25519',
- ssh_transport:default_algorithms(public_key)) of
- true ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_eddsa(ed25519, DataDir, PrivDir),
- Config;
- false ->
- {skip, unsupported_pub_key}
- end;
-init_per_group(ed448_key, Config) ->
- case lists:member('ssh-ed448',
- ssh_transport:default_algorithms(public_key)) of
- true ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_eddsa(ed448, DataDir, PrivDir),
- Config;
- false ->
- {skip, unsupported_pub_key}
- end;
-init_per_group(rsa_pass_key, Config) ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- case lists:member('ssh-rsa',
- ssh_transport:default_algorithms(public_key))
- andalso
- ssh_test_lib:setup_rsa_pass_phrase(DataDir, PrivDir, "Password")
- of
- true ->
- [{pass_phrase, {rsa_pass_phrase, "Password"}}| Config];
- false ->
- {skip, unsupported_pub_key}
- end;
-init_per_group(dsa_pass_key, Config) ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- case lists:member('ssh-dss',
- ssh_transport:default_algorithms(public_key))
- andalso
- ssh_test_lib:setup_dsa_pass_phrase(DataDir, PrivDir, "Password")
- of
- true ->
- [{pass_phrase, {dsa_pass_phrase, "Password"}}| Config];
- false ->
- {skip, unsupported_pub_key}
- end;
-init_per_group(ecdsa_sha2_nistp256_pass_key, Config) ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- case lists:member('ecdsa-sha2-nistp256',
- ssh_transport:default_algorithms(public_key))
- andalso
- ssh_test_lib:setup_ecdsa_pass_phrase("256", DataDir, PrivDir, "Password")
- of
- true ->
- [{pass_phrase, {ecdsa_pass_phrase, "Password"}}| Config];
- false ->
- {skip, unsupported_pub_key}
- end;
-init_per_group(ecdsa_sha2_nistp384_pass_key, Config) ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- case lists:member('ecdsa-sha2-nistp384',
- ssh_transport:default_algorithms(public_key))
- andalso
- ssh_test_lib:setup_ecdsa_pass_phrase("384", DataDir, PrivDir, "Password")
- of
- true ->
- [{pass_phrase, {ecdsa_pass_phrase, "Password"}}| Config];
- false ->
- {skip, unsupported_pub_key}
- end;
-init_per_group(ecdsa_sha2_nistp521_pass_key, Config) ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- case lists:member('ecdsa-sha2-nistp521',
- ssh_transport:default_algorithms(public_key))
- andalso
- ssh_test_lib:setup_ecdsa_pass_phrase("521", DataDir, PrivDir, "Password")
- of
- true ->
- [{pass_phrase, {ecdsa_pass_phrase, "Password"}}| Config];
- false ->
- {skip, unsupported_pub_key}
- end;
-init_per_group(host_user_key_differs, Config) ->
- Data = proplists:get_value(data_dir, Config),
- Sys = filename:join(proplists:get_value(priv_dir, Config), system_rsa),
- SysUsr = filename:join(Sys, user),
- Usr = filename:join(proplists:get_value(priv_dir, Config), user_ecdsa_256),
- file:make_dir(Sys),
- file:make_dir(SysUsr),
- file:make_dir(Usr),
- file:copy(filename:join(Data, "ssh_host_rsa_key"), filename:join(Sys, "ssh_host_rsa_key")),
- file:copy(filename:join(Data, "ssh_host_rsa_key.pub"), filename:join(Sys, "ssh_host_rsa_key.pub")),
- file:copy(filename:join(Data, "id_ecdsa256"), filename:join(Usr, "id_ecdsa")),
- file:copy(filename:join(Data, "id_ecdsa256.pub"), filename:join(Usr, "id_ecdsa.pub")),
- ssh_test_lib:setup_ecdsa_auth_keys("256", Data, SysUsr),
- ssh_test_lib:setup_rsa_known_host(Sys, Usr),
- Config;
init_per_group(key_cb, Config) ->
case lists:member('ssh-rsa',
- ssh_transport:default_algorithms(public_key)) of
+ ssh_transport:supported_algorithms(public_key)) of
true ->
DataDir = proplists:get_value(data_dir, Config),
PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_rsa(DataDir, PrivDir),
+ ssh_test_lib:setup_user_key('ssh-rsa', DataDir, PrivDir),
+ ssh_test_lib:setup_host_key_create_dir('ssh-rsa', DataDir, PrivDir),
Config;
false ->
{skip, unsupported_pub_key}
end;
-init_per_group(internal_error, Config) ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_rsa(DataDir, PrivDir),
- ssh_test_lib:setup_dsa(DataDir, PrivDir),
- ssh_test_lib:setup_ecdsa("256", DataDir, PrivDir),
- %% In the test case the key will be deleted after the daemon start:
- %% ... file:delete(filename:join(PrivDir, "system/ssh_host_dsa_key")),
- Config;
-init_per_group(dir_options, Config) ->
- PrivDir = proplists:get_value(priv_dir, Config),
- %% Make unreadable dir:
- Dir_unreadable = filename:join(PrivDir, "unread"),
- ok = file:make_dir(Dir_unreadable),
- {ok,F1} = file:read_file_info(Dir_unreadable),
- ok = file:write_file_info(Dir_unreadable,
- F1#file_info{mode = F1#file_info.mode band (bnot 8#00444)}),
- %% Make readable file:
- File_readable = filename:join(PrivDir, "file"),
- ok = file:write_file(File_readable, <<>>),
-
- %% Check:
- case {file:read_file_info(Dir_unreadable),
- file:read_file_info(File_readable)} of
- {{ok, Id=#file_info{type=directory, access=Md}},
- {ok, If=#file_info{type=regular, access=Mf}}} ->
- AccessOK =
- case {Md, Mf} of
- {read, _} -> false;
- {read_write, _} -> false;
- {_, read} -> true;
- {_, read_write} -> true;
- _ -> false
- end,
-
- case AccessOK of
- true ->
- %% Save:
- [{unreadable_dir, Dir_unreadable},
- {readable_file, File_readable}
- | Config];
- false ->
- ct:log("File#file_info : ~p~n"
- "Dir#file_info : ~p",[If,Id]),
- {skip, "File or dir mode settings failed"}
- end;
-
- NotDirFile ->
- ct:log("{Dir,File} -> ~p",[NotDirFile]),
- {skip, "File/Dir creation failed"}
- end;
init_per_group(_, Config) ->
Config.
-end_per_group(dsa_key, Config) ->
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:clean_dsa(PrivDir),
- Config;
-end_per_group(rsa_key, Config) ->
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:clean_rsa(PrivDir),
- Config;
-end_per_group(dsa_pass_key, Config) ->
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:clean_dsa(PrivDir),
- Config;
-end_per_group(rsa_pass_key, Config) ->
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:clean_rsa(PrivDir),
- Config;
-end_per_group(key_cb, Config) ->
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:clean_rsa(PrivDir),
- Config;
-end_per_group(internal_error, Config) ->
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:clean_rsa(PrivDir),
- ssh_test_lib:clean_dsa(PrivDir),
- Config;
-
end_per_group(_, Config) ->
Config.
%%--------------------------------------------------------------------
@@ -462,11 +165,6 @@ init_per_testcase(inet6_option, Config) ->
init_per_testcase(_TestCase, Config) ->
Config.
-end_per_testcase(TestCase, Config) when TestCase == server_password_option;
- TestCase == server_userpassword_option ->
- UserDir = filename:join(proplists:get_value(priv_dir, Config), nopubkey),
- ssh_test_lib:del_dirs(UserDir),
- end_per_testcase(Config);
end_per_testcase(TC, Config) when TC==shell_no_unicode ;
TC==shell_unicode_string ->
case proplists:get_value(sftpd, Config) of
@@ -527,6 +225,7 @@ inet_option(Config) when is_list(Config) ->
%%--------------------------------------------------------------------
%%% Test configuring IPv6
+inet6_option() -> [{timetrap,{seconds,30}}].
inet6_option(Config) when is_list(Config) ->
SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
UserDir = proplists:get_value(priv_dir, Config),
@@ -603,13 +302,16 @@ exec_with_io_out(Config) when is_list(Config) ->
"io:write(hej).", infinity),
case ssh_test_lib:receive_exec_result(
[{ssh_cm, ConnectionRef, {data, ChannelId0, 0, <<"hej">>}},
- {ssh_cm, ConnectionRef, {data, ChannelId0, 0, <<"ok">>}}]) of
+ {ssh_cm, ConnectionRef, {data, ChannelId0, 0, <<"ok">>}},
+ {ssh_cm, ConnectionRef, {eof, ChannelId0}},
+ {ssh_cm, ConnectionRef, {exit_status, ChannelId0, 0}},
+ {ssh_cm, ConnectionRef, {closed, ChannelId0}}
+ ]) of
expected ->
ok;
Other0 ->
ct:fail(Other0)
end,
- ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId0),
ssh:close(ConnectionRef),
ssh:stop_daemon(Pid).
@@ -738,81 +440,6 @@ shell(Config) when is_list(Config) ->
end.
%%--------------------------------------------------------------------
-%%% Test that we could user different types of host pubkey and user pubkey
-exec_key_differs1(Config) -> exec_key_differs(Config, ['ecdsa-sha2-nistp256']).
-
-exec_key_differs2(Config) -> exec_key_differs(Config, ['ssh-dss','ecdsa-sha2-nistp256']).
-
-exec_key_differs3(Config) -> exec_key_differs(Config, ['ecdsa-sha2-nistp384','ecdsa-sha2-nistp256']).
-
-
-
-exec_key_differs(Config, UserPKAlgs) ->
- case lists:usort(['ssh-rsa'|UserPKAlgs])
- -- ssh_transport:supported_algorithms(public_key)
- of
- [] ->
- process_flag(trap_exit, true),
- SystemDir = filename:join(proplists:get_value(priv_dir, Config), system_rsa),
- SystemUserDir = filename:join(SystemDir, user),
- UserDir = filename:join(proplists:get_value(priv_dir, Config), user_ecdsa_256),
-
- {_Pid, _Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
- {user_dir, SystemUserDir},
- {preferred_algorithms,
- [{public_key,['ssh-rsa'|UserPKAlgs]}]}]),
- ct:sleep(500),
-
- IO = ssh_test_lib:start_io_server(),
- Shell = ssh_test_lib:start_shell(Port, IO, [{user_dir,UserDir},
- {preferred_algorithms,[{public_key,['ssh-rsa']}]},
- {pref_public_key_algs,UserPKAlgs}
- ]),
-
-
- receive
- {'EXIT', _, _} ->
- ct:fail(no_ssh_connection);
- ErlShellStart ->
- ct:log("Erlang shell start: ~p~n", [ErlShellStart]),
- do_shell(IO, Shell)
- after
- 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
- end;
-
- UnsupportedPubKeys ->
- {skip, io_lib:format("~p unsupported",[UnsupportedPubKeys])}
- end.
-
-%%--------------------------------------------------------------------
-exec_key_differs_fail(Config) when is_list(Config) ->
- process_flag(trap_exit, true),
- SystemDir = filename:join(proplists:get_value(priv_dir, Config), system_rsa),
- SystemUserDir = filename:join(SystemDir, user),
- UserDir = filename:join(proplists:get_value(priv_dir, Config), user_ecdsa_256),
-
- {_Pid, _Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
- {user_dir, SystemUserDir},
- {preferred_algorithms,
- [{public_key,['ssh-rsa']}]}]),
- ct:sleep(500),
-
- IO = ssh_test_lib:start_io_server(),
- ssh_test_lib:start_shell(Port, IO, [{user_dir,UserDir},
- {recv_ext_info, false},
- {preferred_algorithms,[{public_key,['ssh-rsa']}]},
- {pref_public_key_algs,['ssh-dss']}]),
- receive
- {'EXIT', _, _} ->
- ok;
- ErlShellStart ->
- ct:log("Erlang shell start: ~p~n", [ErlShellStart]),
- ct:fail(connection_not_rejected)
- after
- 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
- end.
-
-%%--------------------------------------------------------------------
cli(Config) when is_list(Config) ->
process_flag(trap_exit, true),
SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
@@ -871,6 +498,11 @@ daemon_already_started(Config) when is_list(Config) ->
%%--------------------------------------------------------------------
%%% Test that a failed daemon start does not leave the port open
+
+%%%%%%%%%%%%%%%%%%%%%% REWRITE! %%%%%%%%%%%%%%%%%%%%
+%%% 1) check that {error,_} is not {error,eaddrinuse}
+%%% 2) instead of ssh_test_lib:daemon second time, use gen_tcp:listen
+
daemon_error_closes_port(Config) ->
GoodSystemDir = proplists:get_value(data_dir, Config),
Port = inet_port(),
@@ -886,6 +518,11 @@ daemon_error_closes_port(Config) ->
ssh:stop_daemon(Pid)
end.
+inet_port() ->
+ {ok, Socket} = gen_tcp:listen(0, [{reuseaddr, true}]),
+ {ok, Port} = inet:port(Socket),
+ gen_tcp:close(Socket),
+ Port.
%%--------------------------------------------------------------------
%%% check that known_hosts is updated correctly
@@ -957,7 +594,7 @@ known_hosts(Config) when is_list(Config) ->
ssh:stop_daemon(Pid).
%%--------------------------------------------------------------------
-ssh_file_is_host_key() -> [{timetrap,{seconds,120}}]. % Some machines are S L O W !
+ssh_file_is_host_key() -> [{timetrap,{seconds,240}}]. % Some machines are S L O W !
ssh_file_is_host_key(Config) ->
Dir = ssh_test_lib:create_random_dir(Config),
ct:log("Dir = ~p", [Dir]),
@@ -1176,12 +813,13 @@ send(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},
+ {preferred_algorithms, ssh_transport:supported_algorithms()},
{user_dir, UserDir},
{failfun, fun ssh_test_lib:failfun/2}]),
ConnectionRef =
- ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ ssh_test_lib:connect(Host, Port, [{preferred_algorithms, ssh_transport:supported_algorithms()},
+ {silently_accept_hosts, true},
{user_dir, UserDir},
{user_interaction, false}]),
{ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
@@ -1191,17 +829,6 @@ send(Config) when is_list(Config) ->
%%--------------------------------------------------------------------
-%%%
-fail_daemon_start(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),
-
- {error,_} = ssh_test_lib:daemon([{system_dir, SystemDir},
- {user_dir, UserDir},
- {failfun, fun ssh_test_lib:failfun/2}]).
-
-%%--------------------------------------------------------------------
%%% Test ssh:connection_info([peername, sockname])
peername_sockname(Config) when is_list(Config) ->
process_flag(trap_exit, true),
@@ -1646,315 +1273,6 @@ setopts_getopts(Config) ->
ssh:stop_daemon(Pid).
-%%----------------------------------------------------------------------------
-%%% Idle timeout test
-rekey0() -> [{timetrap,{seconds,90}}].
-rekey1() -> [{timetrap,{seconds,90}}].
-rekey2() -> [{timetrap,{seconds,90}}].
-rekey3() -> [{timetrap,{seconds,90}}].
-rekey4() -> [{timetrap,{seconds,90}}].
-
-rekey0(Config) -> rekey_chk(Config, 0, 0).
-rekey1(Config) -> rekey_chk(Config, infinity, 0).
-rekey2(Config) -> rekey_chk(Config, {infinity,infinity}, 0).
-rekey3(Config) -> rekey_chk(Config, 0, infinity).
-rekey4(Config) -> rekey_chk(Config, 0, {infinity,infinity}).
-
-rekey_chk(Config, RLdaemon, RLclient) ->
- {Pid, Host, Port} = ssh_test_lib:std_daemon(Config, [{rekey_limit, RLdaemon}]),
- ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{rekey_limit, RLclient}]),
- Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
-
- %% Make both sides send something:
- {ok, _SftpPid} = ssh_sftp:start_channel(ConnectionRef),
-
- %% Check rekeying
- timer:sleep(?REKEY_DATA_TMO),
- ?wait_match(false, Kex1==ssh_test_lib:get_kex_init(ConnectionRef), [], 2000, 10),
-
- ssh:close(ConnectionRef),
- ssh:stop_daemon(Pid).
-
-%%--------------------------------------------------------------------
-%%% Test rekeying by data volume
-
-rekey_limit_client() -> [{timetrap,{seconds,400}}].
-rekey_limit_client(Config) ->
- Limit = 6000,
- UserDir = proplists:get_value(priv_dir, Config),
- DataFile = filename:join(UserDir, "rekey.data"),
- Data = lists:duplicate(Limit+10,1),
- Algs = proplists:get_value(preferred_algorithms, Config),
- {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0},
- {preferred_algorithms,Algs}]),
-
- ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{rekey_limit, Limit},
- {max_random_length_padding,0}]),
- {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
-
- %% Check that it doesn't rekey without data transfer
- Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
- timer:sleep(?REKEY_DATA_TMO),
- true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)),
-
- %% Check that datatransfer triggers rekeying
- ok = ssh_sftp:write_file(SftpPid, DataFile, Data),
- timer:sleep(?REKEY_DATA_TMO),
- ?wait_match(false, Kex1==(Kex2=ssh_test_lib:get_kex_init(ConnectionRef)), Kex2, 2000, 10),
-
- %% Check that datatransfer continues to trigger rekeying
- ok = ssh_sftp:write_file(SftpPid, DataFile, Data),
- timer:sleep(?REKEY_DATA_TMO),
- ?wait_match(false, Kex2==(Kex3=ssh_test_lib:get_kex_init(ConnectionRef)), Kex3, 2000, 10),
-
- %% Check that it doesn't rekey without data transfer
- timer:sleep(?REKEY_DATA_TMO),
- true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)),
-
- %% Check that it doesn't rekey on a small datatransfer
- ok = ssh_sftp:write_file(SftpPid, DataFile, "hi\n"),
- timer:sleep(?REKEY_DATA_TMO),
- true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)),
-
- %% Check that it doesn't rekey without data transfer
- timer:sleep(?REKEY_DATA_TMO),
- true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)),
-
- ssh_sftp:stop_channel(SftpPid),
- ssh:close(ConnectionRef),
- ssh:stop_daemon(Pid).
-
-
-
-rekey_limit_daemon() -> [{timetrap,{seconds,400}}].
-rekey_limit_daemon(Config) ->
- Limit = 6000,
- UserDir = proplists:get_value(priv_dir, Config),
- DataFile1 = filename:join(UserDir, "rekey1.data"),
- DataFile2 = filename:join(UserDir, "rekey2.data"),
- file:write_file(DataFile1, lists:duplicate(Limit+10,1)),
- file:write_file(DataFile2, "hi\n"),
-
- Algs = proplists:get_value(preferred_algorithms, Config),
- {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{rekey_limit, Limit},
- {max_random_length_padding,0},
- {preferred_algorithms,Algs}]),
- ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{max_random_length_padding,0}]),
- {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
-
- %% Check that it doesn't rekey without data transfer
- Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
- timer:sleep(?REKEY_DATA_TMO),
- Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
-
- %% Check that datatransfer triggers rekeying
- {ok,_} = ssh_sftp:read_file(SftpPid, DataFile1),
- timer:sleep(?REKEY_DATA_TMO),
- ?wait_match(false, Kex1==(Kex2=ssh_test_lib:get_kex_init(ConnectionRef)), Kex2, 2000, 10),
-
- %% Check that datatransfer continues to trigger rekeying
- {ok,_} = ssh_sftp:read_file(SftpPid, DataFile1),
- timer:sleep(?REKEY_DATA_TMO),
- ?wait_match(false, Kex2==(Kex3=ssh_test_lib:get_kex_init(ConnectionRef)), Kex3, 2000, 10),
-
- %% Check that it doesn't rekey without data transfer
- timer:sleep(?REKEY_DATA_TMO),
- true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)),
-
- %% Check that it doesn't rekey on a small datatransfer
- {ok,_} = ssh_sftp:read_file(SftpPid, DataFile2),
- timer:sleep(?REKEY_DATA_TMO),
- true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)),
-
- %% Check that it doesn't rekey without data transfer
- timer:sleep(?REKEY_DATA_TMO),
- true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)),
-
- ssh_sftp:stop_channel(SftpPid),
- ssh:close(ConnectionRef),
- ssh:stop_daemon(Pid).
-
-
-%%--------------------------------------------------------------------
-%% Check that datatransfer in the other direction does not trigger re-keying
-norekey_limit_client() -> [{timetrap,{seconds,400}}].
-norekey_limit_client(Config) ->
- Limit = 6000,
- UserDir = proplists:get_value(priv_dir, Config),
- DataFile = filename:join(UserDir, "rekey3.data"),
- file:write_file(DataFile, lists:duplicate(Limit+10,1)),
-
- Algs = proplists:get_value(preferred_algorithms, Config),
- {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0},
- {preferred_algorithms,Algs}]),
-
- ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{rekey_limit, Limit},
- {max_random_length_padding,0}]),
- {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
-
- Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
- timer:sleep(?REKEY_DATA_TMO),
- true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)),
-
- {ok,_} = ssh_sftp:read_file(SftpPid, DataFile),
- timer:sleep(?REKEY_DATA_TMO),
- true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)),
-
- ssh_sftp:stop_channel(SftpPid),
- ssh:close(ConnectionRef),
- ssh:stop_daemon(Pid).
-
-%% Check that datatransfer in the other direction does not trigger re-keying
-norekey_limit_daemon() -> [{timetrap,{seconds,400}}].
-norekey_limit_daemon(Config) ->
- Limit = 6000,
- UserDir = proplists:get_value(priv_dir, Config),
- DataFile = filename:join(UserDir, "rekey4.data"),
-
- Algs = proplists:get_value(preferred_algorithms, Config),
- {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{rekey_limit, Limit},
- {max_random_length_padding,0},
- {preferred_algorithms,Algs}]),
-
- ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{max_random_length_padding,0}]),
- {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
-
- Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
- timer:sleep(?REKEY_DATA_TMO),
- true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)),
-
- ok = ssh_sftp:write_file(SftpPid, DataFile, lists:duplicate(Limit+10,1)),
- timer:sleep(?REKEY_DATA_TMO),
- true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)),
-
- ssh_sftp:stop_channel(SftpPid),
- ssh:close(ConnectionRef),
- ssh:stop_daemon(Pid).
-
-%%--------------------------------------------------------------------
-%%% Test rekeying by time
-
-rekey_time_limit_client() -> [{timetrap,{seconds,400}}].
-rekey_time_limit_client(Config) ->
- Minutes = ?REKEY_DATA_TMO div 60000,
- GB = 1024*1000*1000,
- Algs = proplists:get_value(preferred_algorithms, Config),
- {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0},
- {preferred_algorithms,Algs}]),
- ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{rekey_limit, {Minutes, GB}},
- {max_random_length_padding,0}]),
- rekey_time_limit(Pid, ConnectionRef).
-
-rekey_time_limit_daemon() -> [{timetrap,{seconds,400}}].
-rekey_time_limit_daemon(Config) ->
- Minutes = ?REKEY_DATA_TMO div 60000,
- GB = 1024*1000*1000,
- Algs = proplists:get_value(preferred_algorithms, Config),
- {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{rekey_limit, {Minutes, GB}},
- {max_random_length_padding,0},
- {preferred_algorithms,Algs}]),
- ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{max_random_length_padding,0}]),
- rekey_time_limit(Pid, ConnectionRef).
-
-
-rekey_time_limit(Pid, ConnectionRef) ->
- {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
- Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
-
- timer:sleep(5000),
- true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)),
-
- %% Check that it rekeys when the max time + 30s has passed
- timer:sleep(?REKEY_DATA_TMO + 30*1000),
- ?wait_match(false, Kex1==(Kex2=ssh_test_lib:get_kex_init(ConnectionRef)), Kex2, 2000, 10),
-
- %% Check that it does not rekey when nothing is transferred
- timer:sleep(?REKEY_DATA_TMO + 30*1000),
- ?wait_match(false, Kex2==ssh_test_lib:get_kex_init(ConnectionRef), [], 2000, 10),
-
- ssh_sftp:stop_channel(SftpPid),
- ssh:close(ConnectionRef),
- ssh:stop_daemon(Pid).
-
-%%--------------------------------------------------------------------
-
-%%% Test rekeying with simultaneous send request
-
-renegotiate1(Config) ->
- UserDir = proplists:get_value(priv_dir, Config),
- DataFile = filename:join(UserDir, "renegotiate1.data"),
-
- Algs = proplists:get_value(preferred_algorithms, Config),
- {Pid, Host, DPort} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0},
- {preferred_algorithms,Algs}]),
-
- {ok,RelayPid,_,RPort} = ssh_relay:start_link({0,0,0,0}, 0, Host, DPort),
-
- ConnectionRef = ssh_test_lib:std_connect(Config, Host, RPort, [{max_random_length_padding,0}]),
- {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
-
- Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
-
- {ok, Handle} = ssh_sftp:open(SftpPid, DataFile, [write]),
-
- ok = ssh_sftp:write(SftpPid, Handle, "hi\n"),
-
- ssh_relay:hold(RelayPid, rx, 20, 1000),
- ssh_connection_handler:renegotiate(ConnectionRef),
- spawn(fun() -> ok=ssh_sftp:write(SftpPid, Handle, "another hi\n") end),
-
- timer:sleep(2000),
-
- Kex2 = ssh_test_lib:get_kex_init(ConnectionRef),
-
- false = (Kex2 == Kex1),
-
- ssh_relay:stop(RelayPid),
- ssh_sftp:stop_channel(SftpPid),
- ssh:close(ConnectionRef),
- ssh:stop_daemon(Pid).
-
-%%--------------------------------------------------------------------
-
-%%% Test rekeying with inflight messages from peer
-
-renegotiate2(Config) ->
- UserDir = proplists:get_value(priv_dir, Config),
- DataFile = filename:join(UserDir, "renegotiate2.data"),
-
- Algs = proplists:get_value(preferred_algorithms, Config),
- {Pid, Host, DPort} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0},
- {preferred_algorithms,Algs}]),
-
- {ok,RelayPid,_,RPort} = ssh_relay:start_link({0,0,0,0}, 0, Host, DPort),
-
- ConnectionRef = ssh_test_lib:std_connect(Config, Host, RPort, [{max_random_length_padding,0}]),
- {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
-
- Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
-
- {ok, Handle} = ssh_sftp:open(SftpPid, DataFile, [write]),
-
- ok = ssh_sftp:write(SftpPid, Handle, "hi\n"),
-
- ssh_relay:hold(RelayPid, rx, 20, infinity),
- spawn(fun() -> ok=ssh_sftp:write(SftpPid, Handle, "another hi\n") end),
- %% need a small pause here to ensure ssh_sftp:write is executed
- ct:sleep(10),
- ssh_connection_handler:renegotiate(ConnectionRef),
- ssh_relay:release(RelayPid, rx),
-
- timer:sleep(2000),
-
- Kex2 = ssh_test_lib:get_kex_init(ConnectionRef),
-
- false = (Kex2 == Kex1),
-
- ssh_relay:stop(RelayPid),
- ssh_sftp:stop_channel(SftpPid),
- ssh:close(ConnectionRef),
- ssh:stop_daemon(Pid).
-
%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------
@@ -2134,9 +1452,3 @@ new_do_shell_prompt(IO, N, Op, Str, More) ->
ct:log("Matched prompt ~p",[N]),
new_do_shell(IO, N, [{Op,Str}|More]).
-%%--------------------------------------------------------------------
-inet_port() ->
- {ok, Socket} = gen_tcp:listen(0, [{reuseaddr, true}]),
- {ok, Port} = inet:port(Socket),
- gen_tcp:close(Socket),
- Port.
diff --git a/lib/ssh/test/ssh_basic_SUITE_data/id_dsa b/lib/ssh/test/ssh_basic_SUITE_data/id_dsa
index d306f8b26e..24628e071b 100644
--- a/lib/ssh/test/ssh_basic_SUITE_data/id_dsa
+++ b/lib/ssh/test/ssh_basic_SUITE_data/id_dsa
@@ -1,13 +1,12 @@
-----BEGIN DSA PRIVATE KEY-----
-MIIBvAIBAAKBgQDfi2flSTZZofwT4yQT0NikX/LGNT7UPeB/XEWe/xovEYCElfaQ
-APFixXvEgXwoojmZ5kiQRKzLM39wBP0jPERLbnZXfOOD0PDnw0haMh7dD7XKVMod
-/EigVgHf/qBdM2M8yz1s/rRF7n1UpLSypziKjkzCm7JoSQ2zbWIPdmBIXwIVAMgP
-kpr7Sq3O7sHdb8D601DRjoExAoGAMOQxDfB2Fd8ouz6G96f/UOzRMI/Kdv8kYYKW
-JIGY+pRYrLPyYzUeJznwZreOJgrczAX+luHnKFWJ2Dnk5CyeXk67Wsr7pJ/4MBMD
-OKeIS0S8qoSBN8+Krp79fgA+yS3IfqbkJLtLu4EBaCX4mKQIX4++k44d4U5lc8pt
-+9hlEI8CgYEAznKxx9kyC6bVo7LUYKaGhofRFt0SYFc5PVmT2VUGRs1R6+6DPD+e
-uEO6IhFct7JFSRbP9p0JD4Uk+3zlZF+XX6b2PsZkeV8f/02xlNGUSmEzCSiNg1AX
-Cy/WusYhul0MncWCHMcOZB5rIvU/aP5EJJtn3xrRaz6u0SThF6AnT34CFQC63czE
-ZU8w8Q+H7z0j+a+70x2iAw==
+MIIBuwIBAAKBgQDIywHurUpOq6kZuMn+XlRzR4hAxF6qwSkuEqkV7iHnLQ0kIwf3
+uAmjFDhuEsQ8653SLxGVvTNp+KFFgDXiLqgM7TPUwDnpbvzEZHPAU+/zPt4sdY2D
+txBfJwT2SFlK6HPOxOcxdDuD+/a59sh8hk/YVOU7ZTcBVsVG8Got4UcF5QIVAPGd
+CPDQKSTlPiM9OwBB1+9p11k5AoGARLxw4l17mET9cU0uf4Ppe5nsCbODJv44ZrSs
+picvypGVLrLcN5KWbm3vjRFCQ5LFunAG3FwLC2Sh0CH6TemoIfRPsRHR7wvpBGdr
+c693UlMOis/mcmvNMQAzuQNW9WrxdzsvWR/r5s6NEHWqKUJGXSPi2d+Ijq/mCOmI
+hzLzyiACgYEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4
+cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+C
+ROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNgCFEjA7wTC
+sQCY/I35vb6GUJn9tEdP
-----END DSA PRIVATE KEY-----
-
diff --git a/lib/ssh/test/ssh_basic_SUITE_data/id_dsa.pub b/lib/ssh/test/ssh_basic_SUITE_data/id_dsa.pub
new file mode 100644
index 0000000000..018ef6f537
--- /dev/null
+++ b/lib/ssh/test/ssh_basic_SUITE_data/id_dsa.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAMjLAe6tSk6rqRm4yf5eVHNHiEDEXqrBKS4SqRXuIectDSQjB/e4CaMUOG4SxDzrndIvEZW9M2n4oUWANeIuqAztM9TAOelu/MRkc8BT7/M+3ix1jYO3EF8nBPZIWUroc87E5zF0O4P79rn2yHyGT9hU5TtlNwFWxUbwai3hRwXlAAAAFQDxnQjw0Ckk5T4jPTsAQdfvaddZOQAAAIBEvHDiXXuYRP1xTS5/g+l7mewJs4Mm/jhmtKymJy/KkZUustw3kpZube+NEUJDksW6cAbcXAsLZKHQIfpN6agh9E+xEdHvC+kEZ2tzr3dSUw6Kz+Zya80xADO5A1b1avF3Oy9ZH+vmzo0QdaopQkZdI+LZ34iOr+YI6YiHMvPKIAAAAIEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+CROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNg= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_basic_SUITE_data/id_rsa b/lib/ssh/test/ssh_basic_SUITE_data/id_rsa
index 9d7e0dd5fb..2202c2ead8 100644
--- a/lib/ssh/test/ssh_basic_SUITE_data/id_rsa
+++ b/lib/ssh/test/ssh_basic_SUITE_data/id_rsa
@@ -1,15 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
-MIICXAIBAAKBgQD1OET+3O/Bvj/dtjxDTXmj1oiJt4sIph5kGy0RfjoPrZfaS+CU
-DhakCmS6t2ivxWFgtpKWaoGMZMJqWj6F6ZsumyFl3FPBtujwY/35cgifrI9Ns4Tl
-zR1uuengNBmV+WRQ5cd9F2qS6Z8aDQihzt0r8JUqLcK+VQbrmNzboCCQQwIDAQAB
-AoGAPQEyqPTt8JUT7mRXuaacjFXiweAXhp9NEDpyi9eLOjtFe9lElZCrsUOkq47V
-TGUeRKEm9qSodfTbKPoqc8YaBJGJPhUaTAcha+7QcDdfHBvIsgxvU7ePVnlpXRp3
-CCUEMPhlnx6xBoTYP+fRU0e3+xJIPVyVCqX1jAdUMkzfRoECQQD6ux7B1QJAIWyK
-SGkbDUbBilNmzCFNgIpOP6PA+bwfi5d16diTpra5AX09keQABAo/KaP1PdV8Vg0p
-z4P3A7G3AkEA+l+AKG6m0kQTTBMJDqOdVPYwe+5GxunMaqmhokpEbuGsrZBl5Dvd
-WpcBjR7jmenrhKZRIuA+Fz5HPo/UQJPl1QJBAKxstDkeED8j/S2XoFhPKAJ+6t39
-sUVICVTIZQeXdmzHJXCcUSkw8+WEhakqw/3SyW0oaK2FSWQJFWJUZ+8eJj8CQEh3
-xeduB5kKnS9CvzdeghZqX6QvVosSdtlUmfUYW/BgH5PpHKTP8wTaeld3XldZTpMJ
-dKiMkUw2+XYROVUrubUCQD+Na1LhULlpn4ISEtIEfqpdlUhxDgO15Wg8USmsng+x
-ICliVOSQtwaZjm8kwaFt0W7XnpnDxbRs37vIEbIMWak=
+MIIEpAIBAAKCAQEAztjiyj2tdfkji0fewWS0kABg0IABgG20NvL1PnHJLr98we7w
+W7f3j27EGjW/ApuycsWXXKi0L82q8uDicoHHb3JI2JkT70oi0yG1Dx/zwPN+dkA7
+LBT1J3UK2hJTFPhp855CwY/ss9xpBsd1Fv3zuHifEqNGljeg1PjmQ3pNhxA/M0aZ
+cLnfIUdZ5Hr+t+4es3zaWo4tLBKmZu6BkVGQKPGXeMkIAMtJlG24l7qKDRkR5TYA
+ZT7P8Vn7hnuFuCNbrJSm686GawBxTQXom23dg9UcWxoHB7UiHFoR6j0bQAX+4R7b
+IwculRDcvzrgCu6u06oFILwY7MlsxpX9hGTl1wIDAQABAoIBAFeP6pmQeICrYceR
+OhQGLIWVE2bP+VLDnflw6i5v/qlieE6kdm1tOEgorK0nuV9CR81cJdIcvIJL/yTn
+3BR7KdDcwUenrY+rg4h7CWmIrigtK4ilciccDBeS7XAZN8B11GxDv6Cu65XMJU2w
+W7nK8URTE4vRQI1QqS3e26MPAAi/LVOt3ZPI6zg/GHEwnq0IVSQAOndLBr/IWZk5
+SANrkfwX8WS7/UxZgDptT9dyUQ5Pnj5mieTlIvBwyczdhZ7RDa8HdCSHW3xF83V1
+A0pkn6+TRojumYyr4RrPQj6htE64Hgx9w1Dv/UINjPXl5mGlbxQHMWGzlqD/qpyI
+wg7RakECgYEA+9ARZpHfEFz+EEFi8l9J+BtJDo00WaKCOZHh5UJ8W+NreqSd8nSx
+5u6wYwMJjRX2Hwv+FBEhxGbo1+ff6p++cYmiSlDtN2XRCDkBWvvGlxu55BDULrhx
+f8lqaV3XGmOy2rQusp8hiHmkmPJCSVj3oJqQnbqJ2zahXAx1rTPwHqECgYEA0kln
+4h+ZkZ+aldOMGF0d0txTcTqZvsSVKiFTSD9of/fiSDqb6xtLT2+ys6FZoFL9lyK8
+gtqH642CDQ+3WT6Nmn4kMF5HNVpEuCeRDeRhiquWeKaAQDyvZ5ym1+Cn3GhsO7Di
+d2LJKV5hOoN77loVY5nwnUVIJ0h+WLf0T7DTCXcCgYEAiNT7X50MdTvS4splFgcp
+jqRlAn9AXySrVtUqxwVlxhjCIpapLUK0GSTCvEq+OeghIaXGnujgTHUPOaNKTZgY
+SGHdyjxHar7s42b2kZYWx63NSVzLr8eSBTpRlIflhvV+DtGyPmWyNxLCmkmqM2kg
+xii3RL5EgtYgwIAUwdVjOYECgYBRPlsMWfkS8f7fc+PkZdVn6gey71kHAxw+MrHi
+b90H09Vw4nPq2Zi3EAiSrfvanTWsdpcuVw+8See89B16NViwH5wLs+D/E+kI3QCF
+xX6J/NEdu/ZA2zFJbpRnQzyXQyDNzwEv7tKZUQVvfe0boWIyIP99Q48k3jUyQZ/6
+Se6+8QKBgQCXl8H2K3CsZxoujKLb2qoEOPbxJQ2hxoMTS5XuQECReIVsNuptWrur
+DF8WJi/B6AqwRX1P3l56RNwqB1yDBqv0QVLpU7vU/FmWqLWTn0r3AvM74qftvfAE
+oa31wcYoCqPJoKgCG7TThLhNt2v5hL7sVgZNO0ueAiHhJbFLaf7ceg==
-----END RSA PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_basic_SUITE_data/id_rsa.pub b/lib/ssh/test/ssh_basic_SUITE_data/id_rsa.pub
new file mode 100644
index 0000000000..b4084d320a
--- /dev/null
+++ b/lib/ssh/test/ssh_basic_SUITE_data/id_rsa.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDO2OLKPa11+SOLR97BZLSQAGDQgAGAbbQ28vU+cckuv3zB7vBbt/ePbsQaNb8Cm7JyxZdcqLQvzary4OJygcdvckjYmRPvSiLTIbUPH/PA8352QDssFPUndQraElMU+GnznkLBj+yz3GkGx3UW/fO4eJ8So0aWN6DU+OZDek2HED8zRplwud8hR1nkev637h6zfNpaji0sEqZm7oGRUZAo8Zd4yQgAy0mUbbiXuooNGRHlNgBlPs/xWfuGe4W4I1uslKbrzoZrAHFNBeibbd2D1RxbGgcHtSIcWhHqPRtABf7hHtsjBy6VENy/OuAK7q7TqgUgvBjsyWzGlf2EZOXX uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_compat_SUITE.erl b/lib/ssh/test/ssh_compat_SUITE.erl
index a8b3508583..f5f0a35bac 100644
--- a/lib/ssh/test/ssh_compat_SUITE.erl
+++ b/lib/ssh/test/ssh_compat_SUITE.erl
@@ -41,7 +41,7 @@
%%--------------------------------------------------------------------
suite() ->
- [{timetrap,{seconds,60}}].
+ [{timetrap,{seconds,90}}].
all() ->
%% [check_docker_present] ++
@@ -534,21 +534,22 @@ result_of_exec(C, Ch, ExitStatus, Acc) ->
chk_all_algos(FunctionName, CommonAlgs, Config, DoTestFun) when is_function(DoTestFun,2) ->
ct:comment("~p algorithms",[length(CommonAlgs)]),
%% Check each algorithm
- Failed =
+ Nmax = length(CommonAlgs),
+ {_N,Failed} =
lists:foldl(
- fun({Tag,Alg}, FailedAlgos) ->
- %% ct:log("Try ~p",[Alg]),
+ fun({Tag,Alg}, {N,FailedAlgos}) ->
+ ct:log("Try ~p ~p/~p",[{Tag,Alg},N,Nmax]),
case DoTestFun(Tag,Alg) of
{ok,C} ->
ssh:close(C),
- FailedAlgos;
+ {N+1,FailedAlgos};
ok ->
- FailedAlgos;
+ {N+1,FailedAlgos};
Other ->
ct:log("FAILED! ~p ~p: ~p",[Tag,Alg,Other]),
- [{Alg,Other}|FailedAlgos]
+ {N+1, [{Alg,Other}|FailedAlgos]}
end
- end, [], CommonAlgs),
+ end, {1,[]}, CommonAlgs),
ct:pal("~s", [format_result_table_use_all_algos(FunctionName, Config, CommonAlgs, Failed)]),
case Failed of
[] ->
diff --git a/lib/ssh/test/ssh_connection_SUITE.erl b/lib/ssh/test/ssh_connection_SUITE.erl
index d428daaf59..c4621723f6 100644
--- a/lib/ssh/test/ssh_connection_SUITE.erl
+++ b/lib/ssh/test/ssh_connection_SUITE.erl
@@ -1270,37 +1270,6 @@ test_exec_is_enabled(ConnectionRef, Exec, Expect) ->
{fail,"Exec Timeout"}
end.
-
-%%%----------------------------------------------------------------
-get_channel_close_sequence(ConnectionRef, ChannelId) ->
- Set = [eof, exit_status, closed],
- get_channel_close_sequence(ConnectionRef, ChannelId, Set).
-
-get_channel_close_sequence(_ConnectionRef, _ChannelId, []) ->
- ok;
-get_channel_close_sequence(ConnectionRef, ChannelId, Set) ->
- receive
- {ssh_cm, ConnectionRef, Event} when element(2,Event) == ChannelId ->
- try lists:member(element(1,Event), Set)
- of
- true ->
- ct:log("get_channel_close_sequence: ~p received", [Event]),
- get_channel_close_sequence(ConnectionRef, Event, Set--[element(1,Event)]);
- false ->
- ct:log("~p:~p Got unexpected ~p~nExpecting ~p",[?MODULE,?LINE,Event,Set]),
- ct:fail("Strange response 1")
- catch
- _ :_ ->
- ct:log("~p:~p Got unexpected ~p~nExpecting ~p",[?MODULE,?LINE,Event,Set]),
- ct:fail("Strange response 2")
- end;
- Msg ->
- ct:log("~p:~p Got unexpected ~p~nExpecting event from the set ~p",[?MODULE,?LINE,Msg,Set]),
- ct:fail("Strange response 3")
- after
- 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
- end.
-
%%%----------------------------------------------------------------
big_cat_rx(ConnectionRef, ChannelId) ->
big_cat_rx(ConnectionRef, ChannelId, []).
diff --git a/lib/ssh/test/ssh_dbg_SUITE.erl b/lib/ssh/test/ssh_dbg_SUITE.erl
index ab7918fa90..8df9c8093a 100644
--- a/lib/ssh/test/ssh_dbg_SUITE.erl
+++ b/lib/ssh/test/ssh_dbg_SUITE.erl
@@ -82,6 +82,8 @@ end_per_testcase(_TC, Config) ->
ok
after 5000 ->
+ ct:log("~p:~p Messages:~n~p",
+ [?MODULE,?LINE, process_info(self(),messages)]),
ssh_dbg:stop(),
ssh:stop_daemon(Pid),
ct:fail("No '~s' debug message",[ExpectPfx])
@@ -448,10 +450,15 @@ dbg_SKIP(Ref, Prefixes) ->
dbg_SKIP(Ref, Prefixes, UnexpectedAcc) ->
receive
+ {Ref, [_, _C, Msg]} when is_tuple(Msg) ->
+ %% filter non ssh_dbg messages, for example from dbg:tp(..) etc
+ dbg_SKIP(Ref, Prefixes, UnexpectedAcc);
{Ref, [_, _C, Msg]=M} ->
case lists:any(
fun(Pfx) ->
- lists:prefix(Pfx, Msg)
+ try lists:prefix(Pfx, Msg)
+ catch _:_ -> false
+ end
end, Prefixes) of
true ->
ct:log("Skip:~n~p", [M]),
diff --git a/lib/ssh/test/ssh_engine_SUITE.erl b/lib/ssh/test/ssh_engine_SUITE.erl
index 3adb23acdb..241fb255cc 100644
--- a/lib/ssh/test/ssh_engine_SUITE.erl
+++ b/lib/ssh/test/ssh_engine_SUITE.erl
@@ -81,7 +81,7 @@ end_per_suite(Config) ->
%%--------------------------------------------------------------------
init_per_group(dsa_key, Config) ->
case lists:member('ssh-dss',
- ssh_transport:default_algorithms(public_key)) of
+ ssh_transport:supported_algorithms(public_key)) of
true ->
start_daemon(Config, 'ssh-dss', "dsa_private_key.pem");
false ->
@@ -89,7 +89,7 @@ init_per_group(dsa_key, Config) ->
end;
init_per_group(rsa_key, Config) ->
case lists:member('ssh-rsa',
- ssh_transport:default_algorithms(public_key)) of
+ ssh_transport:supported_algorithms(public_key)) of
true ->
start_daemon(Config, 'ssh-rsa', "rsa_private_key.pem");
false ->
@@ -102,9 +102,11 @@ start_daemon(Config, KeyType, KeyId) ->
KeyCBOpts = [{engine, proplists:get_value(engine,Config)},
{KeyType, FullKeyId}
],
- Opts = [{key_cb, {ssh_key_cb_engine_keys, KeyCBOpts}}],
+ Opts = [{key_cb, {ssh_key_cb_engine_keys, KeyCBOpts}},
+ {modify_algorithms, [{append, [{public_key,[KeyType]}]}]}
+ ],
{Pid, Host, Port} = ssh_test_lib:std_daemon(Config, Opts),
- [{host_port,{Host,Port}}, {daemon_pid,Pid}| Config].
+ [{host_port,{Host,Port}}, {daemon_pid,Pid}, {key_type,KeyType}| Config].
end_per_group(_, Config) ->
@@ -118,7 +120,11 @@ end_per_group(_, Config) ->
%% A simple exec call
simple_connect(Config) ->
{Host,Port} = proplists:get_value(host_port, Config),
- CRef = ssh_test_lib:std_connect(Config, Host, Port, []),
+ KeyType = proplists:get_value(key_type, Config),
+ Opts = [
+ {modify_algorithms, [{append, [{public_key,[KeyType]}]}]}
+ ],
+ CRef = ssh_test_lib:std_connect(Config, Host, Port, Opts),
ssh:close(CRef).
%%--------------------------------------------------------------------
@@ -146,8 +152,3 @@ load_engine() ->
{error, Error}
end.
-start_std_daemon(Opts, Config) ->
- ct:log("starting std_daemon",[]),
- {Pid, Host, Port} = ssh_test_lib:std_daemon(Config, Opts),
- ct:log("started ~p:~p ~p",[Host,Port,Opts]),
- [{srvr_pid,Pid},{srvr_addr,{Host,Port}} | Config].
diff --git a/lib/ssh/test/ssh_options_SUITE.erl b/lib/ssh/test/ssh_options_SUITE.erl
index 93c4d7b7a7..4368761d02 100644
--- a/lib/ssh/test/ssh_options_SUITE.erl
+++ b/lib/ssh/test/ssh_options_SUITE.erl
@@ -92,7 +92,7 @@
suite() ->
[{ct_hooks,[ts_install_cth]},
- {timetrap,{seconds,30}}].
+ {timetrap,{seconds,60}}].
all() ->
[connectfun_disconnectfun_server,
@@ -128,9 +128,9 @@ all() ->
id_string_own_string_server_trail_space,
id_string_random_server,
save_accepted_host_option,
- {group, hardening_tests},
config_file,
- config_file_modify_algorithms_order
+ config_file_modify_algorithms_order,
+ {group, hardening_tests}
].
groups() ->
@@ -157,10 +157,8 @@ end_per_suite(_Config) ->
%%--------------------------------------------------------------------
init_per_group(hardening_tests, Config) ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_dsa(DataDir, PrivDir),
- ssh_test_lib:setup_rsa(DataDir, PrivDir),
+ ct:log("Pub keys setup for: ~p",
+ [ssh_test_lib:setup_all_user_host_keys(Config)]),
Config;
init_per_group(dir_options, Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
@@ -937,7 +935,7 @@ ssh_connect_arg4_timeout(_Config) ->
Msp = ms_passed(T0),
exit(Server,hasta_la_vista___baby),
Low = 0.9*Timeout,
- High = 2.5*Timeout,
+ High = 4.0*Timeout,
ct:log("Timeout limits: ~.4f - ~.4f ms, timeout "
"was ~.4f ms, expected ~p ms",[Low,High,Msp,Timeout]),
if
diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_dsa b/lib/ssh/test/ssh_options_SUITE_data/id_dsa
index d306f8b26e..24628e071b 100644
--- a/lib/ssh/test/ssh_options_SUITE_data/id_dsa
+++ b/lib/ssh/test/ssh_options_SUITE_data/id_dsa
@@ -1,13 +1,12 @@
-----BEGIN DSA PRIVATE KEY-----
-MIIBvAIBAAKBgQDfi2flSTZZofwT4yQT0NikX/LGNT7UPeB/XEWe/xovEYCElfaQ
-APFixXvEgXwoojmZ5kiQRKzLM39wBP0jPERLbnZXfOOD0PDnw0haMh7dD7XKVMod
-/EigVgHf/qBdM2M8yz1s/rRF7n1UpLSypziKjkzCm7JoSQ2zbWIPdmBIXwIVAMgP
-kpr7Sq3O7sHdb8D601DRjoExAoGAMOQxDfB2Fd8ouz6G96f/UOzRMI/Kdv8kYYKW
-JIGY+pRYrLPyYzUeJznwZreOJgrczAX+luHnKFWJ2Dnk5CyeXk67Wsr7pJ/4MBMD
-OKeIS0S8qoSBN8+Krp79fgA+yS3IfqbkJLtLu4EBaCX4mKQIX4++k44d4U5lc8pt
-+9hlEI8CgYEAznKxx9kyC6bVo7LUYKaGhofRFt0SYFc5PVmT2VUGRs1R6+6DPD+e
-uEO6IhFct7JFSRbP9p0JD4Uk+3zlZF+XX6b2PsZkeV8f/02xlNGUSmEzCSiNg1AX
-Cy/WusYhul0MncWCHMcOZB5rIvU/aP5EJJtn3xrRaz6u0SThF6AnT34CFQC63czE
-ZU8w8Q+H7z0j+a+70x2iAw==
+MIIBuwIBAAKBgQDIywHurUpOq6kZuMn+XlRzR4hAxF6qwSkuEqkV7iHnLQ0kIwf3
+uAmjFDhuEsQ8653SLxGVvTNp+KFFgDXiLqgM7TPUwDnpbvzEZHPAU+/zPt4sdY2D
+txBfJwT2SFlK6HPOxOcxdDuD+/a59sh8hk/YVOU7ZTcBVsVG8Got4UcF5QIVAPGd
+CPDQKSTlPiM9OwBB1+9p11k5AoGARLxw4l17mET9cU0uf4Ppe5nsCbODJv44ZrSs
+picvypGVLrLcN5KWbm3vjRFCQ5LFunAG3FwLC2Sh0CH6TemoIfRPsRHR7wvpBGdr
+c693UlMOis/mcmvNMQAzuQNW9WrxdzsvWR/r5s6NEHWqKUJGXSPi2d+Ijq/mCOmI
+hzLzyiACgYEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4
+cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+C
+ROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNgCFEjA7wTC
+sQCY/I35vb6GUJn9tEdP
-----END DSA PRIVATE KEY-----
-
diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_dsa.pub b/lib/ssh/test/ssh_options_SUITE_data/id_dsa.pub
new file mode 100644
index 0000000000..018ef6f537
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/id_dsa.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAMjLAe6tSk6rqRm4yf5eVHNHiEDEXqrBKS4SqRXuIectDSQjB/e4CaMUOG4SxDzrndIvEZW9M2n4oUWANeIuqAztM9TAOelu/MRkc8BT7/M+3ix1jYO3EF8nBPZIWUroc87E5zF0O4P79rn2yHyGT9hU5TtlNwFWxUbwai3hRwXlAAAAFQDxnQjw0Ckk5T4jPTsAQdfvaddZOQAAAIBEvHDiXXuYRP1xTS5/g+l7mewJs4Mm/jhmtKymJy/KkZUustw3kpZube+NEUJDksW6cAbcXAsLZKHQIfpN6agh9E+xEdHvC+kEZ2tzr3dSUw6Kz+Zya80xADO5A1b1avF3Oy9ZH+vmzo0QdaopQkZdI+LZ34iOr+YI6YiHMvPKIAAAAIEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+CROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNg= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa256 b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa256
new file mode 100644
index 0000000000..4b1eb12eaa
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa256
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIJfCaBKIIKhjbJl5F8BedqlXOQYDX5ba9Skypllmx/w+oAoGCCqGSM49
+AwEHoUQDQgAE49RbK2xQ/19ji3uDPM7uT4692LbwWF1TiaA9vUuebMGazoW/98br
+N9xZu0L1AWwtEjs3kmJDTB7eJEGXnjUAcQ==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa256.pub b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa256.pub
new file mode 100644
index 0000000000..a0147e60fa
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa256.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOPUWytsUP9fY4t7gzzO7k+Ovdi28FhdU4mgPb1LnmzBms6Fv/fG6zfcWbtC9QFsLRI7N5JiQ0we3iRBl541AHE= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa384 b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa384
new file mode 100644
index 0000000000..4e8aa40959
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa384
@@ -0,0 +1,6 @@
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDCYXb6OSAZyXRfLXOtMo43za197Hdc/T0YKjgQQjwDt6rlRwqTh7v7S
+PV2kXwNGdWigBwYFK4EEACKhZANiAARN2khlJUOOIiwsWHEALwDieeZR96qL4pUd
+ci7aeGaczdUK5jOA9D9zmBZtSYTfO8Cr7ekVghDlcWAIJ/BXcswgQwSEQ6wyfaTF
+8FYfyr4l3u9IirsnyaFzeIgeoNis8Gw=
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa384.pub b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa384.pub
new file mode 100644
index 0000000000..41e722e545
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa384.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBE3aSGUlQ44iLCxYcQAvAOJ55lH3qovilR1yLtp4ZpzN1QrmM4D0P3OYFm1JhN87wKvt6RWCEOVxYAgn8FdyzCBDBIRDrDJ9pMXwVh/KviXe70iKuyfJoXN4iB6g2KzwbA== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa521 b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa521
new file mode 100644
index 0000000000..7196f46e97
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa521
@@ -0,0 +1,7 @@
+-----BEGIN EC PRIVATE KEY-----
+MIHbAgEBBEFMadoz4ckEcClfqXa2tiUuYkJdDfwq+/iFQcpt8ESuEd26IY/vm47Q
+9UzbPkO4ou8xkNsQ3WvCRQBBWtn5O2kUU6AHBgUrgQQAI6GBiQOBhgAEAde5BRu5
+01/jS0jRk212xsb2DxPrxNpgp6IMCV8TA4Eps+8bSqHB091nLiBcP422HXYfuCd7
+XDjSs8ihcmhp0hCRASLqZR9EzW9W/SOt876May1Huj5X+WSO6RLe7vPn9vmf7kHf
+pip6m7M7qp2qGgQ3q2vRwS2K/O6156ohiOlmuuFs
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa521.pub b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa521.pub
new file mode 100644
index 0000000000..8f059120bc
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa521.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAHXuQUbudNf40tI0ZNtdsbG9g8T68TaYKeiDAlfEwOBKbPvG0qhwdPdZy4gXD+Nth12H7gne1w40rPIoXJoadIQkQEi6mUfRM1vVv0jrfO+jGstR7o+V/lkjukS3u7z5/b5n+5B36YqepuzO6qdqhoEN6tr0cEtivzuteeqIYjpZrrhbA== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_ed25519 b/lib/ssh/test/ssh_options_SUITE_data/id_ed25519
new file mode 100644
index 0000000000..401a3e4a9a
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/id_ed25519
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACDm9P8/gC0IOKmwHLSvkmEtS2Xx0RRqUDqC6wY6UgDVnwAAAJg3+6xpN/us
+aQAAAAtzc2gtZWQyNTUxOQAAACDm9P8/gC0IOKmwHLSvkmEtS2Xx0RRqUDqC6wY6UgDVnw
+AAAEBzC/Z2WGJhZ3l3tIBnUc6DCbp+lXY2yc2RRpWQTdf8sub0/z+ALQg4qbActK+SYS1L
+ZfHRFGpQOoLrBjpSANWfAAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_ed25519.pub b/lib/ssh/test/ssh_options_SUITE_data/id_ed25519.pub
new file mode 100644
index 0000000000..a5c03b19c1
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/id_ed25519.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOb0/z+ALQg4qbActK+SYS1LZfHRFGpQOoLrBjpSANWf uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_ed448 b/lib/ssh/test/ssh_options_SUITE_data/id_ed448
new file mode 100644
index 0000000000..8ecfd710dc
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/id_ed448
@@ -0,0 +1,10 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz
+c2gtZWQ0NDgAAAA53OqeePNaG/NJmoMbELhskKrAHNhLZ6AQm1WjbpMoseNl/OFh
+1xznExpUPqTLX36fHYsAaWRHABQAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA
+ADnc6p5481ob80magxsQuGyQqsAc2EtnoBCbVaNukyix42X84WHXHOcTGlQ+pMtf
+fp8diwBpZEcAFAAAAAByzSPST3FCdOdENDI3uTKQ9RH2Ql+Y5kRZ/yA+iYUIP/32
+BQBVOrwOBc0CGEvbicTM1n4YeVEmfrMo3OqeePNaG/NJmoMbELhskKrAHNhLZ6AQ
+m1WjbpMoseNl/OFh1xznExpUPqTLX36fHYsAaWRHABQAAAAAAAECAwQ=
+-----END OPENSSH PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_ed448.pub b/lib/ssh/test/ssh_options_SUITE_data/id_ed448.pub
new file mode 100644
index 0000000000..cec0765a5d
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/id_ed448.pub
@@ -0,0 +1 @@
+ssh-ed448 AAAACXNzaC1lZDQ0OAAAADnc6p5481ob80magxsQuGyQqsAc2EtnoBCbVaNukyix42X84WHXHOcTGlQ+pMtffp8diwBpZEcAFAA= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_rsa b/lib/ssh/test/ssh_options_SUITE_data/id_rsa
index 9d7e0dd5fb..2202c2ead8 100644
--- a/lib/ssh/test/ssh_options_SUITE_data/id_rsa
+++ b/lib/ssh/test/ssh_options_SUITE_data/id_rsa
@@ -1,15 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
-MIICXAIBAAKBgQD1OET+3O/Bvj/dtjxDTXmj1oiJt4sIph5kGy0RfjoPrZfaS+CU
-DhakCmS6t2ivxWFgtpKWaoGMZMJqWj6F6ZsumyFl3FPBtujwY/35cgifrI9Ns4Tl
-zR1uuengNBmV+WRQ5cd9F2qS6Z8aDQihzt0r8JUqLcK+VQbrmNzboCCQQwIDAQAB
-AoGAPQEyqPTt8JUT7mRXuaacjFXiweAXhp9NEDpyi9eLOjtFe9lElZCrsUOkq47V
-TGUeRKEm9qSodfTbKPoqc8YaBJGJPhUaTAcha+7QcDdfHBvIsgxvU7ePVnlpXRp3
-CCUEMPhlnx6xBoTYP+fRU0e3+xJIPVyVCqX1jAdUMkzfRoECQQD6ux7B1QJAIWyK
-SGkbDUbBilNmzCFNgIpOP6PA+bwfi5d16diTpra5AX09keQABAo/KaP1PdV8Vg0p
-z4P3A7G3AkEA+l+AKG6m0kQTTBMJDqOdVPYwe+5GxunMaqmhokpEbuGsrZBl5Dvd
-WpcBjR7jmenrhKZRIuA+Fz5HPo/UQJPl1QJBAKxstDkeED8j/S2XoFhPKAJ+6t39
-sUVICVTIZQeXdmzHJXCcUSkw8+WEhakqw/3SyW0oaK2FSWQJFWJUZ+8eJj8CQEh3
-xeduB5kKnS9CvzdeghZqX6QvVosSdtlUmfUYW/BgH5PpHKTP8wTaeld3XldZTpMJ
-dKiMkUw2+XYROVUrubUCQD+Na1LhULlpn4ISEtIEfqpdlUhxDgO15Wg8USmsng+x
-ICliVOSQtwaZjm8kwaFt0W7XnpnDxbRs37vIEbIMWak=
+MIIEpAIBAAKCAQEAztjiyj2tdfkji0fewWS0kABg0IABgG20NvL1PnHJLr98we7w
+W7f3j27EGjW/ApuycsWXXKi0L82q8uDicoHHb3JI2JkT70oi0yG1Dx/zwPN+dkA7
+LBT1J3UK2hJTFPhp855CwY/ss9xpBsd1Fv3zuHifEqNGljeg1PjmQ3pNhxA/M0aZ
+cLnfIUdZ5Hr+t+4es3zaWo4tLBKmZu6BkVGQKPGXeMkIAMtJlG24l7qKDRkR5TYA
+ZT7P8Vn7hnuFuCNbrJSm686GawBxTQXom23dg9UcWxoHB7UiHFoR6j0bQAX+4R7b
+IwculRDcvzrgCu6u06oFILwY7MlsxpX9hGTl1wIDAQABAoIBAFeP6pmQeICrYceR
+OhQGLIWVE2bP+VLDnflw6i5v/qlieE6kdm1tOEgorK0nuV9CR81cJdIcvIJL/yTn
+3BR7KdDcwUenrY+rg4h7CWmIrigtK4ilciccDBeS7XAZN8B11GxDv6Cu65XMJU2w
+W7nK8URTE4vRQI1QqS3e26MPAAi/LVOt3ZPI6zg/GHEwnq0IVSQAOndLBr/IWZk5
+SANrkfwX8WS7/UxZgDptT9dyUQ5Pnj5mieTlIvBwyczdhZ7RDa8HdCSHW3xF83V1
+A0pkn6+TRojumYyr4RrPQj6htE64Hgx9w1Dv/UINjPXl5mGlbxQHMWGzlqD/qpyI
+wg7RakECgYEA+9ARZpHfEFz+EEFi8l9J+BtJDo00WaKCOZHh5UJ8W+NreqSd8nSx
+5u6wYwMJjRX2Hwv+FBEhxGbo1+ff6p++cYmiSlDtN2XRCDkBWvvGlxu55BDULrhx
+f8lqaV3XGmOy2rQusp8hiHmkmPJCSVj3oJqQnbqJ2zahXAx1rTPwHqECgYEA0kln
+4h+ZkZ+aldOMGF0d0txTcTqZvsSVKiFTSD9of/fiSDqb6xtLT2+ys6FZoFL9lyK8
+gtqH642CDQ+3WT6Nmn4kMF5HNVpEuCeRDeRhiquWeKaAQDyvZ5ym1+Cn3GhsO7Di
+d2LJKV5hOoN77loVY5nwnUVIJ0h+WLf0T7DTCXcCgYEAiNT7X50MdTvS4splFgcp
+jqRlAn9AXySrVtUqxwVlxhjCIpapLUK0GSTCvEq+OeghIaXGnujgTHUPOaNKTZgY
+SGHdyjxHar7s42b2kZYWx63NSVzLr8eSBTpRlIflhvV+DtGyPmWyNxLCmkmqM2kg
+xii3RL5EgtYgwIAUwdVjOYECgYBRPlsMWfkS8f7fc+PkZdVn6gey71kHAxw+MrHi
+b90H09Vw4nPq2Zi3EAiSrfvanTWsdpcuVw+8See89B16NViwH5wLs+D/E+kI3QCF
+xX6J/NEdu/ZA2zFJbpRnQzyXQyDNzwEv7tKZUQVvfe0boWIyIP99Q48k3jUyQZ/6
+Se6+8QKBgQCXl8H2K3CsZxoujKLb2qoEOPbxJQ2hxoMTS5XuQECReIVsNuptWrur
+DF8WJi/B6AqwRX1P3l56RNwqB1yDBqv0QVLpU7vU/FmWqLWTn0r3AvM74qftvfAE
+oa31wcYoCqPJoKgCG7TThLhNt2v5hL7sVgZNO0ueAiHhJbFLaf7ceg==
-----END RSA PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_rsa.pub b/lib/ssh/test/ssh_options_SUITE_data/id_rsa.pub
new file mode 100644
index 0000000000..b4084d320a
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/id_rsa.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDO2OLKPa11+SOLR97BZLSQAGDQgAGAbbQ28vU+cckuv3zB7vBbt/ePbsQaNb8Cm7JyxZdcqLQvzary4OJygcdvckjYmRPvSiLTIbUPH/PA8352QDssFPUndQraElMU+GnznkLBj+yz3GkGx3UW/fO4eJ8So0aWN6DU+OZDek2HED8zRplwud8hR1nkev637h6zfNpaji0sEqZm7oGRUZAo8Zd4yQgAy0mUbbiXuooNGRHlNgBlPs/xWfuGe4W4I1uslKbrzoZrAHFNBeibbd2D1RxbGgcHtSIcWhHqPRtABf7hHtsjBy6VENy/OuAK7q7TqgUgvBjsyWzGlf2EZOXX uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key256 b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key256
new file mode 100644
index 0000000000..2979ea88ed
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key256
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIMe4MDoit0t8RzSVPwkCBemQ9fhXL+xnTSAWISw8HNCioAoGCCqGSM49
+AwEHoUQDQgAEo2q7U3P6r0W5WGOLtM78UQtofM9UalEhiZeDdiyylsR/RR17Op0s
+VPGSADLmzzgcucLEKy17j2S+oz42VUJy5A==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key256.pub b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key256.pub
new file mode 100644
index 0000000000..85dc419345
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key256.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKNqu1Nz+q9FuVhji7TO/FELaHzPVGpRIYmXg3YsspbEf0UdezqdLFTxkgAy5s84HLnCxCste49kvqM+NlVCcuQ= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key384 b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key384
new file mode 100644
index 0000000000..fb1a862ded
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key384
@@ -0,0 +1,6 @@
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDArxbDfh3p1okrD9wQw6jJ4d4DdlBPD5GqXE8bIeRJiK41Sh40LgvPw
+mkqEDSXK++CgBwYFK4EEACKhZANiAAScl43Ih2lWTDKrSox5ve5uiTXil4smsup3
+CfS1XPjKxgBAmlfBim8izbdrT0BFdQzz2joduNMtpt61wO4rGs6jm0UP7Kim9PC7
+Hneb/99fIYopdMH5NMnk60zGO1uZ2vc=
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key384.pub b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key384.pub
new file mode 100644
index 0000000000..428d5fb7d7
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key384.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBJyXjciHaVZMMqtKjHm97m6JNeKXiyay6ncJ9LVc+MrGAECaV8GKbyLNt2tPQEV1DPPaOh240y2m3rXA7isazqObRQ/sqKb08Lsed5v/318hiil0wfk0yeTrTMY7W5na9w== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key521 b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key521
new file mode 100644
index 0000000000..3e51ec2ecd
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key521
@@ -0,0 +1,7 @@
+-----BEGIN EC PRIVATE KEY-----
+MIHcAgEBBEIB8O1BFkl2HQjQLRLonEZ97da/h39DMa9/0/hvPZWAI8gUPEQcHxRx
+U7b09p3Zh+EBbMFq8+1ae9ds+ZTxE4WFSvKgBwYFK4EEACOhgYkDgYYABAAlWVjq
+Bzg7Wt4gE6UNb1lRE2cnlmH2L/A5uo6qZRx5lPnSKOxEhxSb/Oay1+9d6KRdrh6/
+vlhd9SHDBhLcAPDvWgBnJIEj92Q3pXX4JtoitL0yl+SvvU+vUh966mzHShHzj8p5
+ccOgPkPNoA70yrpGzkIhPezpZOQdCaOXj/jFqNCTDg==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key521.pub b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key521.pub
new file mode 100644
index 0000000000..017a29f4da
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key521.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAAlWVjqBzg7Wt4gE6UNb1lRE2cnlmH2L/A5uo6qZRx5lPnSKOxEhxSb/Oay1+9d6KRdrh6/vlhd9SHDBhLcAPDvWgBnJIEj92Q3pXX4JtoitL0yl+SvvU+vUh966mzHShHzj8p5ccOgPkPNoA70yrpGzkIhPezpZOQdCaOXj/jFqNCTDg== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed25519_key b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed25519_key
new file mode 100644
index 0000000000..13a8fcf491
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed25519_key
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQAAAJi+h4O7voeD
+uwAAAAtzc2gtZWQyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQ
+AAAEBaOcJfGPNemKc1wPHTCmM4Kwvh6dZ0CqY14UT361UnN0lI66JgZZo72XJ7wGBp+h3W
+TDw/pxXddvaomAIHrIl9AAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed25519_key.pub b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed25519_key.pub
new file mode 100644
index 0000000000..156ef4045c
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed25519_key.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIElI66JgZZo72XJ7wGBp+h3WTDw/pxXddvaomAIHrIl9 uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed448_key b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed448_key
new file mode 100644
index 0000000000..31a7e4e8c3
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed448_key
@@ -0,0 +1,10 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz
+c2gtZWQ0NDgAAAA5X9dEm1m0Yf0s54fsYWrUah2hNCSFpw4fig6nXYDpZ3jt8SR2
+m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA
+ADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHl
+D2zR+hq+r+glYYAAAABybIKlYsuAjRDWMr6JyFE+v2ySnzTd+oyfY8mWDvbjSKNS
+jIo/zC8ETjmj/FuUSS+PAy51SaIAmPlbX9dEm1m0Yf0s54fsYWrUah2hNCSFpw4f
+ig6nXYDpZ3jt8SR2m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAAAAECAwQ=
+-----END OPENSSH PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed448_key.pub b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed448_key.pub
new file mode 100644
index 0000000000..8c390dcb58
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed448_key.pub
@@ -0,0 +1 @@
+ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA=
diff --git a/lib/ssh/test/ssh_protocol_SUITE.erl b/lib/ssh/test/ssh_protocol_SUITE.erl
index 228e0dcbd8..3a08523039 100644
--- a/lib/ssh/test/ssh_protocol_SUITE.erl
+++ b/lib/ssh/test/ssh_protocol_SUITE.erl
@@ -907,10 +907,8 @@ stop_apps(_Config) ->
setup_dirs(Config) ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_dsa(DataDir, PrivDir),
- ssh_test_lib:setup_rsa(DataDir, PrivDir),
+ ct:log("Pub keys setup for: ~p",
+ [ssh_test_lib:setup_all_user_host_keys(Config)]),
Config.
system_dir(Config) -> filename:join(proplists:get_value(priv_dir, Config), system).
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_dsa b/lib/ssh/test/ssh_protocol_SUITE_data/id_dsa
index d306f8b26e..24628e071b 100644
--- a/lib/ssh/test/ssh_protocol_SUITE_data/id_dsa
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_dsa
@@ -1,13 +1,12 @@
-----BEGIN DSA PRIVATE KEY-----
-MIIBvAIBAAKBgQDfi2flSTZZofwT4yQT0NikX/LGNT7UPeB/XEWe/xovEYCElfaQ
-APFixXvEgXwoojmZ5kiQRKzLM39wBP0jPERLbnZXfOOD0PDnw0haMh7dD7XKVMod
-/EigVgHf/qBdM2M8yz1s/rRF7n1UpLSypziKjkzCm7JoSQ2zbWIPdmBIXwIVAMgP
-kpr7Sq3O7sHdb8D601DRjoExAoGAMOQxDfB2Fd8ouz6G96f/UOzRMI/Kdv8kYYKW
-JIGY+pRYrLPyYzUeJznwZreOJgrczAX+luHnKFWJ2Dnk5CyeXk67Wsr7pJ/4MBMD
-OKeIS0S8qoSBN8+Krp79fgA+yS3IfqbkJLtLu4EBaCX4mKQIX4++k44d4U5lc8pt
-+9hlEI8CgYEAznKxx9kyC6bVo7LUYKaGhofRFt0SYFc5PVmT2VUGRs1R6+6DPD+e
-uEO6IhFct7JFSRbP9p0JD4Uk+3zlZF+XX6b2PsZkeV8f/02xlNGUSmEzCSiNg1AX
-Cy/WusYhul0MncWCHMcOZB5rIvU/aP5EJJtn3xrRaz6u0SThF6AnT34CFQC63czE
-ZU8w8Q+H7z0j+a+70x2iAw==
+MIIBuwIBAAKBgQDIywHurUpOq6kZuMn+XlRzR4hAxF6qwSkuEqkV7iHnLQ0kIwf3
+uAmjFDhuEsQ8653SLxGVvTNp+KFFgDXiLqgM7TPUwDnpbvzEZHPAU+/zPt4sdY2D
+txBfJwT2SFlK6HPOxOcxdDuD+/a59sh8hk/YVOU7ZTcBVsVG8Got4UcF5QIVAPGd
+CPDQKSTlPiM9OwBB1+9p11k5AoGARLxw4l17mET9cU0uf4Ppe5nsCbODJv44ZrSs
+picvypGVLrLcN5KWbm3vjRFCQ5LFunAG3FwLC2Sh0CH6TemoIfRPsRHR7wvpBGdr
+c693UlMOis/mcmvNMQAzuQNW9WrxdzsvWR/r5s6NEHWqKUJGXSPi2d+Ijq/mCOmI
+hzLzyiACgYEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4
+cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+C
+ROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNgCFEjA7wTC
+sQCY/I35vb6GUJn9tEdP
-----END DSA PRIVATE KEY-----
-
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_dsa.pub b/lib/ssh/test/ssh_protocol_SUITE_data/id_dsa.pub
new file mode 100644
index 0000000000..018ef6f537
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_dsa.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAMjLAe6tSk6rqRm4yf5eVHNHiEDEXqrBKS4SqRXuIectDSQjB/e4CaMUOG4SxDzrndIvEZW9M2n4oUWANeIuqAztM9TAOelu/MRkc8BT7/M+3ix1jYO3EF8nBPZIWUroc87E5zF0O4P79rn2yHyGT9hU5TtlNwFWxUbwai3hRwXlAAAAFQDxnQjw0Ckk5T4jPTsAQdfvaddZOQAAAIBEvHDiXXuYRP1xTS5/g+l7mewJs4Mm/jhmtKymJy/KkZUustw3kpZube+NEUJDksW6cAbcXAsLZKHQIfpN6agh9E+xEdHvC+kEZ2tzr3dSUw6Kz+Zya80xADO5A1b1avF3Oy9ZH+vmzo0QdaopQkZdI+LZ34iOr+YI6YiHMvPKIAAAAIEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+CROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNg= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa256 b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa256
new file mode 100644
index 0000000000..4b1eb12eaa
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa256
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIJfCaBKIIKhjbJl5F8BedqlXOQYDX5ba9Skypllmx/w+oAoGCCqGSM49
+AwEHoUQDQgAE49RbK2xQ/19ji3uDPM7uT4692LbwWF1TiaA9vUuebMGazoW/98br
+N9xZu0L1AWwtEjs3kmJDTB7eJEGXnjUAcQ==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa256.pub b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa256.pub
new file mode 100644
index 0000000000..a0147e60fa
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa256.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOPUWytsUP9fY4t7gzzO7k+Ovdi28FhdU4mgPb1LnmzBms6Fv/fG6zfcWbtC9QFsLRI7N5JiQ0we3iRBl541AHE= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa384 b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa384
new file mode 100644
index 0000000000..4e8aa40959
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa384
@@ -0,0 +1,6 @@
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDCYXb6OSAZyXRfLXOtMo43za197Hdc/T0YKjgQQjwDt6rlRwqTh7v7S
+PV2kXwNGdWigBwYFK4EEACKhZANiAARN2khlJUOOIiwsWHEALwDieeZR96qL4pUd
+ci7aeGaczdUK5jOA9D9zmBZtSYTfO8Cr7ekVghDlcWAIJ/BXcswgQwSEQ6wyfaTF
+8FYfyr4l3u9IirsnyaFzeIgeoNis8Gw=
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa384.pub b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa384.pub
new file mode 100644
index 0000000000..41e722e545
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa384.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBE3aSGUlQ44iLCxYcQAvAOJ55lH3qovilR1yLtp4ZpzN1QrmM4D0P3OYFm1JhN87wKvt6RWCEOVxYAgn8FdyzCBDBIRDrDJ9pMXwVh/KviXe70iKuyfJoXN4iB6g2KzwbA== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa521 b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa521
new file mode 100644
index 0000000000..7196f46e97
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa521
@@ -0,0 +1,7 @@
+-----BEGIN EC PRIVATE KEY-----
+MIHbAgEBBEFMadoz4ckEcClfqXa2tiUuYkJdDfwq+/iFQcpt8ESuEd26IY/vm47Q
+9UzbPkO4ou8xkNsQ3WvCRQBBWtn5O2kUU6AHBgUrgQQAI6GBiQOBhgAEAde5BRu5
+01/jS0jRk212xsb2DxPrxNpgp6IMCV8TA4Eps+8bSqHB091nLiBcP422HXYfuCd7
+XDjSs8ihcmhp0hCRASLqZR9EzW9W/SOt876May1Huj5X+WSO6RLe7vPn9vmf7kHf
+pip6m7M7qp2qGgQ3q2vRwS2K/O6156ohiOlmuuFs
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa521.pub b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa521.pub
new file mode 100644
index 0000000000..8f059120bc
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa521.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAHXuQUbudNf40tI0ZNtdsbG9g8T68TaYKeiDAlfEwOBKbPvG0qhwdPdZy4gXD+Nth12H7gne1w40rPIoXJoadIQkQEi6mUfRM1vVv0jrfO+jGstR7o+V/lkjukS3u7z5/b5n+5B36YqepuzO6qdqhoEN6tr0cEtivzuteeqIYjpZrrhbA== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_ed25519 b/lib/ssh/test/ssh_protocol_SUITE_data/id_ed25519
new file mode 100644
index 0000000000..401a3e4a9a
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_ed25519
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACDm9P8/gC0IOKmwHLSvkmEtS2Xx0RRqUDqC6wY6UgDVnwAAAJg3+6xpN/us
+aQAAAAtzc2gtZWQyNTUxOQAAACDm9P8/gC0IOKmwHLSvkmEtS2Xx0RRqUDqC6wY6UgDVnw
+AAAEBzC/Z2WGJhZ3l3tIBnUc6DCbp+lXY2yc2RRpWQTdf8sub0/z+ALQg4qbActK+SYS1L
+ZfHRFGpQOoLrBjpSANWfAAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_ed25519.pub b/lib/ssh/test/ssh_protocol_SUITE_data/id_ed25519.pub
new file mode 100644
index 0000000000..a5c03b19c1
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_ed25519.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOb0/z+ALQg4qbActK+SYS1LZfHRFGpQOoLrBjpSANWf uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_ed448 b/lib/ssh/test/ssh_protocol_SUITE_data/id_ed448
new file mode 100644
index 0000000000..8ecfd710dc
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_ed448
@@ -0,0 +1,10 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz
+c2gtZWQ0NDgAAAA53OqeePNaG/NJmoMbELhskKrAHNhLZ6AQm1WjbpMoseNl/OFh
+1xznExpUPqTLX36fHYsAaWRHABQAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA
+ADnc6p5481ob80magxsQuGyQqsAc2EtnoBCbVaNukyix42X84WHXHOcTGlQ+pMtf
+fp8diwBpZEcAFAAAAAByzSPST3FCdOdENDI3uTKQ9RH2Ql+Y5kRZ/yA+iYUIP/32
+BQBVOrwOBc0CGEvbicTM1n4YeVEmfrMo3OqeePNaG/NJmoMbELhskKrAHNhLZ6AQ
+m1WjbpMoseNl/OFh1xznExpUPqTLX36fHYsAaWRHABQAAAAAAAECAwQ=
+-----END OPENSSH PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_ed448.pub b/lib/ssh/test/ssh_protocol_SUITE_data/id_ed448.pub
new file mode 100644
index 0000000000..cec0765a5d
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_ed448.pub
@@ -0,0 +1 @@
+ssh-ed448 AAAACXNzaC1lZDQ0OAAAADnc6p5481ob80magxsQuGyQqsAc2EtnoBCbVaNukyix42X84WHXHOcTGlQ+pMtffp8diwBpZEcAFAA= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_rsa b/lib/ssh/test/ssh_protocol_SUITE_data/id_rsa
index 9d7e0dd5fb..2202c2ead8 100644
--- a/lib/ssh/test/ssh_protocol_SUITE_data/id_rsa
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_rsa
@@ -1,15 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
-MIICXAIBAAKBgQD1OET+3O/Bvj/dtjxDTXmj1oiJt4sIph5kGy0RfjoPrZfaS+CU
-DhakCmS6t2ivxWFgtpKWaoGMZMJqWj6F6ZsumyFl3FPBtujwY/35cgifrI9Ns4Tl
-zR1uuengNBmV+WRQ5cd9F2qS6Z8aDQihzt0r8JUqLcK+VQbrmNzboCCQQwIDAQAB
-AoGAPQEyqPTt8JUT7mRXuaacjFXiweAXhp9NEDpyi9eLOjtFe9lElZCrsUOkq47V
-TGUeRKEm9qSodfTbKPoqc8YaBJGJPhUaTAcha+7QcDdfHBvIsgxvU7ePVnlpXRp3
-CCUEMPhlnx6xBoTYP+fRU0e3+xJIPVyVCqX1jAdUMkzfRoECQQD6ux7B1QJAIWyK
-SGkbDUbBilNmzCFNgIpOP6PA+bwfi5d16diTpra5AX09keQABAo/KaP1PdV8Vg0p
-z4P3A7G3AkEA+l+AKG6m0kQTTBMJDqOdVPYwe+5GxunMaqmhokpEbuGsrZBl5Dvd
-WpcBjR7jmenrhKZRIuA+Fz5HPo/UQJPl1QJBAKxstDkeED8j/S2XoFhPKAJ+6t39
-sUVICVTIZQeXdmzHJXCcUSkw8+WEhakqw/3SyW0oaK2FSWQJFWJUZ+8eJj8CQEh3
-xeduB5kKnS9CvzdeghZqX6QvVosSdtlUmfUYW/BgH5PpHKTP8wTaeld3XldZTpMJ
-dKiMkUw2+XYROVUrubUCQD+Na1LhULlpn4ISEtIEfqpdlUhxDgO15Wg8USmsng+x
-ICliVOSQtwaZjm8kwaFt0W7XnpnDxbRs37vIEbIMWak=
+MIIEpAIBAAKCAQEAztjiyj2tdfkji0fewWS0kABg0IABgG20NvL1PnHJLr98we7w
+W7f3j27EGjW/ApuycsWXXKi0L82q8uDicoHHb3JI2JkT70oi0yG1Dx/zwPN+dkA7
+LBT1J3UK2hJTFPhp855CwY/ss9xpBsd1Fv3zuHifEqNGljeg1PjmQ3pNhxA/M0aZ
+cLnfIUdZ5Hr+t+4es3zaWo4tLBKmZu6BkVGQKPGXeMkIAMtJlG24l7qKDRkR5TYA
+ZT7P8Vn7hnuFuCNbrJSm686GawBxTQXom23dg9UcWxoHB7UiHFoR6j0bQAX+4R7b
+IwculRDcvzrgCu6u06oFILwY7MlsxpX9hGTl1wIDAQABAoIBAFeP6pmQeICrYceR
+OhQGLIWVE2bP+VLDnflw6i5v/qlieE6kdm1tOEgorK0nuV9CR81cJdIcvIJL/yTn
+3BR7KdDcwUenrY+rg4h7CWmIrigtK4ilciccDBeS7XAZN8B11GxDv6Cu65XMJU2w
+W7nK8URTE4vRQI1QqS3e26MPAAi/LVOt3ZPI6zg/GHEwnq0IVSQAOndLBr/IWZk5
+SANrkfwX8WS7/UxZgDptT9dyUQ5Pnj5mieTlIvBwyczdhZ7RDa8HdCSHW3xF83V1
+A0pkn6+TRojumYyr4RrPQj6htE64Hgx9w1Dv/UINjPXl5mGlbxQHMWGzlqD/qpyI
+wg7RakECgYEA+9ARZpHfEFz+EEFi8l9J+BtJDo00WaKCOZHh5UJ8W+NreqSd8nSx
+5u6wYwMJjRX2Hwv+FBEhxGbo1+ff6p++cYmiSlDtN2XRCDkBWvvGlxu55BDULrhx
+f8lqaV3XGmOy2rQusp8hiHmkmPJCSVj3oJqQnbqJ2zahXAx1rTPwHqECgYEA0kln
+4h+ZkZ+aldOMGF0d0txTcTqZvsSVKiFTSD9of/fiSDqb6xtLT2+ys6FZoFL9lyK8
+gtqH642CDQ+3WT6Nmn4kMF5HNVpEuCeRDeRhiquWeKaAQDyvZ5ym1+Cn3GhsO7Di
+d2LJKV5hOoN77loVY5nwnUVIJ0h+WLf0T7DTCXcCgYEAiNT7X50MdTvS4splFgcp
+jqRlAn9AXySrVtUqxwVlxhjCIpapLUK0GSTCvEq+OeghIaXGnujgTHUPOaNKTZgY
+SGHdyjxHar7s42b2kZYWx63NSVzLr8eSBTpRlIflhvV+DtGyPmWyNxLCmkmqM2kg
+xii3RL5EgtYgwIAUwdVjOYECgYBRPlsMWfkS8f7fc+PkZdVn6gey71kHAxw+MrHi
+b90H09Vw4nPq2Zi3EAiSrfvanTWsdpcuVw+8See89B16NViwH5wLs+D/E+kI3QCF
+xX6J/NEdu/ZA2zFJbpRnQzyXQyDNzwEv7tKZUQVvfe0boWIyIP99Q48k3jUyQZ/6
+Se6+8QKBgQCXl8H2K3CsZxoujKLb2qoEOPbxJQ2hxoMTS5XuQECReIVsNuptWrur
+DF8WJi/B6AqwRX1P3l56RNwqB1yDBqv0QVLpU7vU/FmWqLWTn0r3AvM74qftvfAE
+oa31wcYoCqPJoKgCG7TThLhNt2v5hL7sVgZNO0ueAiHhJbFLaf7ceg==
-----END RSA PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_rsa.pub b/lib/ssh/test/ssh_protocol_SUITE_data/id_rsa.pub
new file mode 100644
index 0000000000..b4084d320a
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_rsa.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDO2OLKPa11+SOLR97BZLSQAGDQgAGAbbQ28vU+cckuv3zB7vBbt/ePbsQaNb8Cm7JyxZdcqLQvzary4OJygcdvckjYmRPvSiLTIbUPH/PA8352QDssFPUndQraElMU+GnznkLBj+yz3GkGx3UW/fO4eJ8So0aWN6DU+OZDek2HED8zRplwud8hR1nkev637h6zfNpaji0sEqZm7oGRUZAo8Zd4yQgAy0mUbbiXuooNGRHlNgBlPs/xWfuGe4W4I1uslKbrzoZrAHFNBeibbd2D1RxbGgcHtSIcWhHqPRtABf7hHtsjBy6VENy/OuAK7q7TqgUgvBjsyWzGlf2EZOXX uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key256 b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key256
new file mode 100644
index 0000000000..2979ea88ed
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key256
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIMe4MDoit0t8RzSVPwkCBemQ9fhXL+xnTSAWISw8HNCioAoGCCqGSM49
+AwEHoUQDQgAEo2q7U3P6r0W5WGOLtM78UQtofM9UalEhiZeDdiyylsR/RR17Op0s
+VPGSADLmzzgcucLEKy17j2S+oz42VUJy5A==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key256.pub b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key256.pub
new file mode 100644
index 0000000000..85dc419345
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key256.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKNqu1Nz+q9FuVhji7TO/FELaHzPVGpRIYmXg3YsspbEf0UdezqdLFTxkgAy5s84HLnCxCste49kvqM+NlVCcuQ= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key384 b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key384
new file mode 100644
index 0000000000..fb1a862ded
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key384
@@ -0,0 +1,6 @@
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDArxbDfh3p1okrD9wQw6jJ4d4DdlBPD5GqXE8bIeRJiK41Sh40LgvPw
+mkqEDSXK++CgBwYFK4EEACKhZANiAAScl43Ih2lWTDKrSox5ve5uiTXil4smsup3
+CfS1XPjKxgBAmlfBim8izbdrT0BFdQzz2joduNMtpt61wO4rGs6jm0UP7Kim9PC7
+Hneb/99fIYopdMH5NMnk60zGO1uZ2vc=
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key384.pub b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key384.pub
new file mode 100644
index 0000000000..428d5fb7d7
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key384.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBJyXjciHaVZMMqtKjHm97m6JNeKXiyay6ncJ9LVc+MrGAECaV8GKbyLNt2tPQEV1DPPaOh240y2m3rXA7isazqObRQ/sqKb08Lsed5v/318hiil0wfk0yeTrTMY7W5na9w== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key521 b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key521
new file mode 100644
index 0000000000..3e51ec2ecd
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key521
@@ -0,0 +1,7 @@
+-----BEGIN EC PRIVATE KEY-----
+MIHcAgEBBEIB8O1BFkl2HQjQLRLonEZ97da/h39DMa9/0/hvPZWAI8gUPEQcHxRx
+U7b09p3Zh+EBbMFq8+1ae9ds+ZTxE4WFSvKgBwYFK4EEACOhgYkDgYYABAAlWVjq
+Bzg7Wt4gE6UNb1lRE2cnlmH2L/A5uo6qZRx5lPnSKOxEhxSb/Oay1+9d6KRdrh6/
+vlhd9SHDBhLcAPDvWgBnJIEj92Q3pXX4JtoitL0yl+SvvU+vUh966mzHShHzj8p5
+ccOgPkPNoA70yrpGzkIhPezpZOQdCaOXj/jFqNCTDg==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key521.pub b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key521.pub
new file mode 100644
index 0000000000..017a29f4da
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key521.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAAlWVjqBzg7Wt4gE6UNb1lRE2cnlmH2L/A5uo6qZRx5lPnSKOxEhxSb/Oay1+9d6KRdrh6/vlhd9SHDBhLcAPDvWgBnJIEj92Q3pXX4JtoitL0yl+SvvU+vUh966mzHShHzj8p5ccOgPkPNoA70yrpGzkIhPezpZOQdCaOXj/jFqNCTDg== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed25519_key b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed25519_key
new file mode 100644
index 0000000000..13a8fcf491
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed25519_key
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQAAAJi+h4O7voeD
+uwAAAAtzc2gtZWQyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQ
+AAAEBaOcJfGPNemKc1wPHTCmM4Kwvh6dZ0CqY14UT361UnN0lI66JgZZo72XJ7wGBp+h3W
+TDw/pxXddvaomAIHrIl9AAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed25519_key.pub b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed25519_key.pub
new file mode 100644
index 0000000000..156ef4045c
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed25519_key.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIElI66JgZZo72XJ7wGBp+h3WTDw/pxXddvaomAIHrIl9 uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed448_key b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed448_key
new file mode 100644
index 0000000000..31a7e4e8c3
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed448_key
@@ -0,0 +1,10 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz
+c2gtZWQ0NDgAAAA5X9dEm1m0Yf0s54fsYWrUah2hNCSFpw4fig6nXYDpZ3jt8SR2
+m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA
+ADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHl
+D2zR+hq+r+glYYAAAABybIKlYsuAjRDWMr6JyFE+v2ySnzTd+oyfY8mWDvbjSKNS
+jIo/zC8ETjmj/FuUSS+PAy51SaIAmPlbX9dEm1m0Yf0s54fsYWrUah2hNCSFpw4f
+ig6nXYDpZ3jt8SR2m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAAAAECAwQ=
+-----END OPENSSH PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed448_key.pub b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed448_key.pub
new file mode 100644
index 0000000000..8c390dcb58
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed448_key.pub
@@ -0,0 +1 @@
+ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA=
diff --git a/lib/ssh/test/ssh_pubkey_SUITE.erl b/lib/ssh/test/ssh_pubkey_SUITE.erl
index fbc17e5028..f1afb496c1 100644
--- a/lib/ssh/test/ssh_pubkey_SUITE.erl
+++ b/lib/ssh/test/ssh_pubkey_SUITE.erl
@@ -36,7 +36,8 @@ suite() ->
all() ->
[{group, old_format},
- {group, new_format}
+ {group, new_format},
+ {group, option_space}
].
@@ -51,19 +52,31 @@ all() ->
connect_ecdsa_to_ecdsa
]).
--define(tests_new, [connect_ecdsa_to_ed25519,
- connect_rsa_to_ed25519,
+-define(tests_new, [
connect_dsa_to_ed25519,
- connect_ed25519_to_rsa,
+ connect_dsa_to_ed448,
+ connect_ecdsa_to_ed25519,
+ connect_ecdsa_to_ed448,
connect_ed25519_to_dsa,
connect_ed25519_to_ecdsa,
- connect_ed25519_to_ed25519
- | ?tests_old]).
+ connect_ed25519_to_ed448,
+ connect_ed25519_to_ed25519,
+ connect_ed25519_to_rsa,
+ connect_ed448_to_dsa,
+ connect_ed448_to_ecdsa,
+ connect_ed448_to_ed25519,
+ connect_ed448_to_ed448,
+ connect_ed448_to_rsa,
+ connect_rsa_to_ed25519,
+ connect_rsa_to_ed448
+ | ?tests_old % but taken from the new format directory
+ ]).
groups() ->
- [{new_format, [], ?tests_new},
- {old_format, [], ?tests_old++[{group,passphrase}]},
- {passphrase, [], ?tests_old}
+ [{new_format, [], ?tests_new},
+ {old_format, [], ?tests_old++[{group,passphrase}]},
+ {passphrase, [], ?tests_old},
+ {option_space,[], [{group,new_format}]}
].
%%%----------------------------------------------------------------
@@ -71,7 +84,8 @@ init_per_suite(Config) ->
?CHECK_CRYPTO(
begin
ssh:start(),
- [{client_opts,[]}
+ [{client_opts,[]},
+ {daemon_opts,[]}
| Config]
end).
@@ -89,6 +103,11 @@ init_per_group(old_format, Config) ->
[{fmt,old_format},
{key_src_dir,Dir} | Config];
+init_per_group(option_space, Config) ->
+ extend_optsL([client_opts,daemon_opts],
+ [{key_cb, {ssh_file, [{optimize, space}]}}],
+ Config);
+
init_per_group(passphrase, Config0) ->
case supported(hashs, md5) of
true ->
@@ -110,7 +129,11 @@ extend_opts(OptName, Value, Config) ->
Opts = proplists:get_value(OptName, Config),
replace_opt(OptName, [Value|Opts], Config).
-extend_optsL(OptName, Values, Config) ->
+extend_optsL(OptNames, Values, Config) when is_list(OptNames) ->
+ lists:foldl(fun(N, Cnf) ->
+ extend_optsL(N, Values, Cnf)
+ end, Config, OptNames);
+extend_optsL(OptName, Values, Config) when is_atom(OptName) ->
Opts = proplists:get_value(OptName, Config),
replace_opt(OptName, Values ++ Opts, Config).
@@ -131,6 +154,8 @@ init_per_testcase(connect_rsa_to_ecdsa, Config0) ->
setup_user_system_dir(rsa, ecdsa, Config0);
init_per_testcase(connect_rsa_to_ed25519, Config0) ->
setup_user_system_dir(rsa, ed25519, Config0);
+init_per_testcase(connect_rsa_to_ed448, Config0) ->
+ setup_user_system_dir(rsa, ed448, Config0);
init_per_testcase(connect_dsa_to_rsa, Config0) ->
setup_user_system_dir(dsa, rsa, Config0);
init_per_testcase(connect_dsa_to_dsa, Config0) ->
@@ -139,6 +164,8 @@ init_per_testcase(connect_dsa_to_ecdsa, Config0) ->
setup_user_system_dir(dsa, ecdsa, Config0);
init_per_testcase(connect_dsa_to_ed25519, Config0) ->
setup_user_system_dir(dsa, ed25519, Config0);
+init_per_testcase(connect_dsa_to_ed448, Config0) ->
+ setup_user_system_dir(dsa, ed448, Config0);
init_per_testcase(connect_ecdsa_to_rsa, Config0) ->
setup_user_system_dir(ecdsa, rsa, Config0);
init_per_testcase(connect_ecdsa_to_dsa, Config0) ->
@@ -147,6 +174,8 @@ init_per_testcase(connect_ecdsa_to_ecdsa, Config0) ->
setup_user_system_dir(ecdsa, ecdsa, Config0);
init_per_testcase(connect_ecdsa_to_ed25519, Config0) ->
setup_user_system_dir(ecdsa, ed25519, Config0);
+init_per_testcase(connect_ecdsa_to_ed448, Config0) ->
+ setup_user_system_dir(ecdsa, ed448, Config0);
init_per_testcase(connect_ed25519_to_rsa, Config0) ->
setup_user_system_dir(ed25519, rsa, Config0);
init_per_testcase(connect_ed25519_to_dsa, Config0) ->
@@ -155,6 +184,18 @@ init_per_testcase(connect_ed25519_to_ecdsa, Config0) ->
setup_user_system_dir(ed25519, ecdsa, Config0);
init_per_testcase(connect_ed25519_to_ed25519, Config0) ->
setup_user_system_dir(ed25519, ed25519, Config0);
+init_per_testcase(connect_ed25519_to_ed448, Config0) ->
+ setup_user_system_dir(ed25519, ed448, Config0);
+init_per_testcase(connect_ed448_to_rsa, Config0) ->
+ setup_user_system_dir(ed448, rsa, Config0);
+init_per_testcase(connect_ed448_to_dsa, Config0) ->
+ setup_user_system_dir(ed448, dsa, Config0);
+init_per_testcase(connect_ed448_to_ecdsa, Config0) ->
+ setup_user_system_dir(ed448, ecdsa, Config0);
+init_per_testcase(connect_ed448_to_ed25519, Config0) ->
+ setup_user_system_dir(ed448, ed25519, Config0);
+init_per_testcase(connect_ed448_to_ed448, Config0) ->
+ setup_user_system_dir(ed448, ed448, Config0);
init_per_testcase(_, Config) ->
Config.
@@ -176,6 +217,9 @@ connect_rsa_to_ecdsa(Config) ->
connect_rsa_to_ed25519(Config) ->
try_connect(Config).
+connect_rsa_to_ed448(Config) ->
+ try_connect(Config).
+
connect_dsa_to_rsa(Config) ->
try_connect(Config).
@@ -188,6 +232,9 @@ connect_dsa_to_ecdsa(Config) ->
connect_dsa_to_ed25519(Config) ->
try_connect(Config).
+connect_dsa_to_ed448(Config) ->
+ try_connect(Config).
+
connect_ecdsa_to_rsa(Config) ->
try_connect(Config).
@@ -200,6 +247,9 @@ connect_ecdsa_to_ecdsa(Config) ->
connect_ecdsa_to_ed25519(Config) ->
try_connect(Config).
+connect_ecdsa_to_ed448(Config) ->
+ try_connect(Config).
+
connect_ed25519_to_rsa(Config) ->
try_connect(Config).
@@ -212,6 +262,24 @@ connect_ed25519_to_ecdsa(Config) ->
connect_ed25519_to_ed25519(Config) ->
try_connect(Config).
+connect_ed25519_to_ed448(Config) ->
+ try_connect(Config).
+
+connect_ed448_to_rsa(Config) ->
+ try_connect(Config).
+
+connect_ed448_to_dsa(Config) ->
+ try_connect(Config).
+
+connect_ed448_to_ecdsa(Config) ->
+ try_connect(Config).
+
+connect_ed448_to_ed25519(Config) ->
+ try_connect(Config).
+
+connect_ed448_to_ed448(Config) ->
+ try_connect(Config).
+
%%%----------------------------------------------------------------
try_connect({skip,Reson}) ->
@@ -220,9 +288,12 @@ try_connect(Config) ->
SystemDir = proplists:get_value(system_dir, Config),
UserDir = proplists:get_value(user_dir, Config),
ClientOpts = proplists:get_value(client_opts, Config, []),
+ DaemonOpts = proplists:get_value(daemon_opts, Config, []),
{Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
- {user_dir, UserDir}]),
+ {user_dir, UserDir}
+ | DaemonOpts]),
+
C = ssh_test_lib:connect(Host, Port, [{user_dir, UserDir},
{silently_accept_hosts, true},
{user_interaction, false}
@@ -250,18 +321,32 @@ setup_user_system_dir(ClientAlg, ServerAlg, Config) ->
HostSrcFile = filename:join(KeySrcDir, file(host,ServerAlg)),
HostDstFile = filename:join(SystemDir, file(host,ServerAlg)),
- {ok,_} = file:copy(HostSrcFile, HostDstFile),
UserSrcFile = filename:join(KeySrcDir, file(user,ClientAlg)),
UserDstFile = filename:join(UserDir, file(user,ClientAlg)),
- {ok,_} = file:copy(UserSrcFile, UserDstFile),
UserPubSrcFile = filename:join(KeySrcDir, file(user,ClientAlg)++".pub"),
AuthorizedKeys = filename:join(UserDir, "authorized_keys"),
- {ok,_} = file:copy(UserPubSrcFile, AuthorizedKeys),
- [{system_dir,SystemDir},
- {user_dir,UserDir} | Config];
+ try
+ {ok,_} = file:copy(UserSrcFile, UserDstFile),
+ {ok,_} = file:copy(UserPubSrcFile, AuthorizedKeys),
+ {ok,_} = file:copy(HostSrcFile, HostDstFile)
+ of
+ _ ->
+ ModAlgs = [{modify_algorithms,
+ [{append,[{public_key,
+ lists:usort([alg(ClientAlg),
+ alg(ServerAlg)])}]}]}
+ ],
+ [{system_dir,SystemDir},
+ {user_dir,UserDir}
+ | extend_optsL([daemon_opts,client_opts], ModAlgs, Config)]
+ catch
+ error:{badmatch,{error,enoent}}:S ->
+ ct:log("~p:~p Stack:~n~p", [?MODULE,?LINE,S]),
+ {skip, no_key_file_found}
+ end;
false ->
{skip, unsupported_algorithm}
@@ -271,12 +356,20 @@ setup_user_system_dir(ClientAlg, ServerAlg, Config) ->
file(host, dsa) -> "ssh_host_dsa_key";
file(host, ecdsa) -> "ssh_host_ecdsa_key";
file(host, ed25519) -> "ssh_host_ed25519_key";
+file(host, ed448) -> "ssh_host_ed448_key";
file(host, rsa) -> "ssh_host_rsa_key";
file(user, dsa) -> "id_dsa";
file(user, ecdsa) -> "id_ecdsa";
file(user, ed25519) -> "id_ed25519";
+file(user, ed448) -> "id_ed448";
file(user, rsa) -> "id_rsa".
+alg(dsa) -> 'ssh-dss';
+alg(ecdsa) -> 'ecdsa-sha2-nistp256';
+alg(ed25519) -> 'ssh-ed25519';
+alg(ed448) -> 'ssh-ed448';
+alg(rsa) -> 'ssh-rsa'.
+
supported(public_keys, rsa) -> supported(public_key, 'ssh-rsa') orelse
supported(public_key, 'rsa-sha2-256') orelse
@@ -288,7 +381,7 @@ supported(public_keys, ecdsa) -> supported(public_key, 'ecdsa-sha2-nistp256')
supported(public_keys, ed448) -> supported(public_key, 'ssh-ed448');
supported(public_keys, ed25519) -> supported(public_key, 'ssh-ed25519');
supported(Type, Alg) ->
- case proplists:get_value(Type,ssh:default_algorithms()) of
+ case proplists:get_value(Type,ssh_transport:supported_algorithms()) of
undefined ->
lists:member(Alg, crypto:supports(Type));
L ->
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed448 b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed448
new file mode 100644
index 0000000000..fc8e79e8f0
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed448
@@ -0,0 +1,15 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+A: Key
+SomeKey: Very long \
+line \
+spanning more lines \
+
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz
+c2gtZWQ0NDgAAAA5X9dEm1m0Yf0s54fsYWrUah2hNCSFpw4fig6nXYDpZ3jt8SR2
+m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA
+ADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHl
+D2zR+hq+r+glYYAAAABybIKlYsuAjRDWMr6JyFE+v2ySnzTd+oyfY8mWDvbjSKNS
+jIo/zC8ETjmj/FuUSS+PAy51SaIAmPlbX9dEm1m0Yf0s54fsYWrUah2hNCSFpw4f
+ig6nXYDpZ3jt8SR2m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAAAAECAwQ=
+-----END OPENSSH PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed448.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed448.pub
new file mode 100644
index 0000000000..8c390dcb58
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed448.pub
@@ -0,0 +1 @@
+ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA=
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed448_key b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed448_key
new file mode 100644
index 0000000000..fc8e79e8f0
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed448_key
@@ -0,0 +1,15 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+A: Key
+SomeKey: Very long \
+line \
+spanning more lines \
+
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz
+c2gtZWQ0NDgAAAA5X9dEm1m0Yf0s54fsYWrUah2hNCSFpw4fig6nXYDpZ3jt8SR2
+m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA
+ADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHl
+D2zR+hq+r+glYYAAAABybIKlYsuAjRDWMr6JyFE+v2ySnzTd+oyfY8mWDvbjSKNS
+jIo/zC8ETjmj/FuUSS+PAy51SaIAmPlbX9dEm1m0Yf0s54fsYWrUah2hNCSFpw4f
+ig6nXYDpZ3jt8SR2m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAAAAECAwQ=
+-----END OPENSSH PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed448_key.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed448_key.pub
new file mode 100644
index 0000000000..8c390dcb58
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed448_key.pub
@@ -0,0 +1 @@
+ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA=
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE.erl b/lib/ssh/test/ssh_renegotiate_SUITE.erl
new file mode 100644
index 0000000000..b08a5ab1eb
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE.erl
@@ -0,0 +1,393 @@
+%%
+%% %CopyrightBegin%
+%%
+%% 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.
+%% 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(ssh_renegotiate_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("kernel/include/inet.hrl").
+-include_lib("kernel/include/file.hrl").
+-include("ssh_test_lib.hrl").
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-define(NEWLINE, <<"\r\n">>).
+
+-define(REKEY_DATA_TMO, 1 * 60000). % Should be multiples of 60000
+
+%%--------------------------------------------------------------------
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
+
+suite() ->
+ [{ct_hooks,[ts_install_cth]},
+ {timetrap,{seconds,90}}].
+
+all() ->
+ [{group, renegotiate}
+ ].
+
+groups() ->
+ [{renegotiate, [parallel], [rekey0,
+ rekey1,
+ rekey2,
+ rekey3,
+ rekey4,
+ rekey_limit_client,
+ rekey_limit_daemon,
+ rekey_time_limit_client,
+ rekey_time_limit_daemon,
+ norekey_limit_client,
+ norekey_limit_daemon,
+ renegotiate1,
+ renegotiate2]}
+ ].
+
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ ?CHECK_CRYPTO(begin
+ ssh:start(),
+ ct:log("Pub keys setup for: ~p",
+ [ssh_test_lib:setup_all_user_host_keys(Config)]),
+ [{preferred_algorithms,ssh_transport:supported_algorithms()}
+ | Config]
+ end).
+
+end_per_suite(_Config) ->
+ ssh:stop().
+
+
+init_per_group(_, Config) -> Config.
+
+end_per_group(_, Config) -> Config.
+%%----------------------------------------------------------------------------
+%%% Idle timeout test
+rekey0() -> [{timetrap,{seconds,120}}].
+rekey1() -> [{timetrap,{seconds,120}}].
+rekey2() -> [{timetrap,{seconds,120}}].
+rekey3() -> [{timetrap,{seconds,120}}].
+rekey4() -> [{timetrap,{seconds,120}}].
+
+rekey0(Config) -> rekey_chk(Config, 0, 0).
+rekey1(Config) -> rekey_chk(Config, infinity, 0).
+rekey2(Config) -> rekey_chk(Config, {infinity,infinity}, 0).
+rekey3(Config) -> rekey_chk(Config, 0, infinity).
+rekey4(Config) -> rekey_chk(Config, 0, {infinity,infinity}).
+
+rekey_chk(Config, RLdaemon, RLclient) ->
+ {Pid, Host, Port} = ssh_test_lib:std_daemon(Config, [{rekey_limit, RLdaemon}]),
+ ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{rekey_limit, RLclient}]),
+ Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
+
+ %% Make both sides send something:
+ {ok, _SftpPid} = ssh_sftp:start_channel(ConnectionRef),
+
+ %% Check rekeying
+ timer:sleep(?REKEY_DATA_TMO),
+ ?wait_match(false, Kex1==ssh_test_lib:get_kex_init(ConnectionRef), [], 2000, 10),
+
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid).
+
+%%--------------------------------------------------------------------
+%%% Test rekeying by data volume
+
+rekey_limit_client() -> [{timetrap,{seconds,500}}].
+rekey_limit_client(Config) ->
+ Limit = 6000,
+ UserDir = proplists:get_value(priv_dir, Config),
+ DataFile = filename:join(UserDir, "rekey.data"),
+ Data = lists:duplicate(Limit+10,1),
+ Algs = proplists:get_value(preferred_algorithms, Config),
+ {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0},
+ {preferred_algorithms,Algs}]),
+
+ ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{rekey_limit, Limit},
+ {max_random_length_padding,0}]),
+ {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
+
+ %% Check that it doesn't rekey without data transfer
+ Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
+ timer:sleep(?REKEY_DATA_TMO),
+ true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)),
+
+ %% Check that datatransfer triggers rekeying
+ ok = ssh_sftp:write_file(SftpPid, DataFile, Data),
+ timer:sleep(?REKEY_DATA_TMO),
+ ?wait_match(false, Kex1==(Kex2=ssh_test_lib:get_kex_init(ConnectionRef)), Kex2, 2000, 10),
+
+ %% Check that datatransfer continues to trigger rekeying
+ ok = ssh_sftp:write_file(SftpPid, DataFile, Data),
+ timer:sleep(?REKEY_DATA_TMO),
+ ?wait_match(false, Kex2==(Kex3=ssh_test_lib:get_kex_init(ConnectionRef)), Kex3, 2000, 10),
+
+ %% Check that it doesn't rekey without data transfer
+ timer:sleep(?REKEY_DATA_TMO),
+ true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)),
+
+ %% Check that it doesn't rekey on a small datatransfer
+ ok = ssh_sftp:write_file(SftpPid, DataFile, "hi\n"),
+ timer:sleep(?REKEY_DATA_TMO),
+ true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)),
+
+ %% Check that it doesn't rekey without data transfer
+ timer:sleep(?REKEY_DATA_TMO),
+ true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)),
+
+ ssh_sftp:stop_channel(SftpPid),
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid).
+
+
+
+rekey_limit_daemon() -> [{timetrap,{seconds,500}}].
+rekey_limit_daemon(Config) ->
+ Limit = 6000,
+ UserDir = proplists:get_value(priv_dir, Config),
+ DataFile1 = filename:join(UserDir, "rekey1.data"),
+ DataFile2 = filename:join(UserDir, "rekey2.data"),
+ file:write_file(DataFile1, lists:duplicate(Limit+10,1)),
+ file:write_file(DataFile2, "hi\n"),
+
+ Algs = proplists:get_value(preferred_algorithms, Config),
+ {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{rekey_limit, Limit},
+ {max_random_length_padding,0},
+ {preferred_algorithms,Algs}]),
+ ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{max_random_length_padding,0}]),
+ {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
+
+ %% Check that it doesn't rekey without data transfer
+ Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
+ timer:sleep(?REKEY_DATA_TMO),
+ Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
+
+ %% Check that datatransfer triggers rekeying
+ {ok,_} = ssh_sftp:read_file(SftpPid, DataFile1),
+ timer:sleep(?REKEY_DATA_TMO),
+ ?wait_match(false, Kex1==(Kex2=ssh_test_lib:get_kex_init(ConnectionRef)), Kex2, 2000, 10),
+
+ %% Check that datatransfer continues to trigger rekeying
+ {ok,_} = ssh_sftp:read_file(SftpPid, DataFile1),
+ timer:sleep(?REKEY_DATA_TMO),
+ ?wait_match(false, Kex2==(Kex3=ssh_test_lib:get_kex_init(ConnectionRef)), Kex3, 2000, 10),
+
+ %% Check that it doesn't rekey without data transfer
+ timer:sleep(?REKEY_DATA_TMO),
+ true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)),
+
+ %% Check that it doesn't rekey on a small datatransfer
+ {ok,_} = ssh_sftp:read_file(SftpPid, DataFile2),
+ timer:sleep(?REKEY_DATA_TMO),
+ true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)),
+
+ %% Check that it doesn't rekey without data transfer
+ timer:sleep(?REKEY_DATA_TMO),
+ true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)),
+
+ ssh_sftp:stop_channel(SftpPid),
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid).
+
+
+%%--------------------------------------------------------------------
+%% Check that datatransfer in the other direction does not trigger re-keying
+norekey_limit_client() -> [{timetrap,{seconds,500}}].
+norekey_limit_client(Config) ->
+ Limit = 6000,
+ UserDir = proplists:get_value(priv_dir, Config),
+ DataFile = filename:join(UserDir, "rekey3.data"),
+ file:write_file(DataFile, lists:duplicate(Limit+10,1)),
+
+ Algs = proplists:get_value(preferred_algorithms, Config),
+ {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0},
+ {preferred_algorithms,Algs}]),
+
+ ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{rekey_limit, Limit},
+ {max_random_length_padding,0}]),
+ {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
+
+ Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
+ timer:sleep(?REKEY_DATA_TMO),
+ true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)),
+
+ {ok,_} = ssh_sftp:read_file(SftpPid, DataFile),
+ timer:sleep(?REKEY_DATA_TMO),
+ true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)),
+
+ ssh_sftp:stop_channel(SftpPid),
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid).
+
+%% Check that datatransfer in the other direction does not trigger re-keying
+norekey_limit_daemon() -> [{timetrap,{seconds,500}}].
+norekey_limit_daemon(Config) ->
+ Limit = 6000,
+ UserDir = proplists:get_value(priv_dir, Config),
+ DataFile = filename:join(UserDir, "rekey4.data"),
+
+ Algs = proplists:get_value(preferred_algorithms, Config),
+ {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{rekey_limit, Limit},
+ {max_random_length_padding,0},
+ {preferred_algorithms,Algs}]),
+
+ ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{max_random_length_padding,0}]),
+ {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
+
+ Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
+ timer:sleep(?REKEY_DATA_TMO),
+ true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)),
+
+ ok = ssh_sftp:write_file(SftpPid, DataFile, lists:duplicate(Limit+10,1)),
+ timer:sleep(?REKEY_DATA_TMO),
+ true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)),
+
+ ssh_sftp:stop_channel(SftpPid),
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid).
+
+%%--------------------------------------------------------------------
+%%% Test rekeying by time
+
+rekey_time_limit_client() -> [{timetrap,{seconds,500}}].
+rekey_time_limit_client(Config) ->
+ Minutes = ?REKEY_DATA_TMO div 60000,
+ GB = 1024*1000*1000,
+ Algs = proplists:get_value(preferred_algorithms, Config),
+ {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0},
+ {preferred_algorithms,Algs}]),
+ ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{rekey_limit, {Minutes, GB}},
+ {max_random_length_padding,0}]),
+ rekey_time_limit(Pid, ConnectionRef).
+
+rekey_time_limit_daemon() -> [{timetrap,{seconds,500}}].
+rekey_time_limit_daemon(Config) ->
+ Minutes = ?REKEY_DATA_TMO div 60000,
+ GB = 1024*1000*1000,
+ Algs = proplists:get_value(preferred_algorithms, Config),
+ {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{rekey_limit, {Minutes, GB}},
+ {max_random_length_padding,0},
+ {preferred_algorithms,Algs}]),
+ ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{max_random_length_padding,0}]),
+ rekey_time_limit(Pid, ConnectionRef).
+
+
+rekey_time_limit(Pid, ConnectionRef) ->
+ {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
+ Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
+
+ timer:sleep(5000),
+ true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)),
+
+ %% Check that it rekeys when the max time + 30s has passed
+ timer:sleep(?REKEY_DATA_TMO + 30*1000),
+ ?wait_match(false, Kex1==(Kex2=ssh_test_lib:get_kex_init(ConnectionRef)), Kex2, 2000, 10),
+
+ %% Check that it does not rekey when nothing is transferred
+ timer:sleep(?REKEY_DATA_TMO + 30*1000),
+ ?wait_match(false, Kex2==ssh_test_lib:get_kex_init(ConnectionRef), [], 2000, 10),
+
+ ssh_sftp:stop_channel(SftpPid),
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid).
+
+%%--------------------------------------------------------------------
+
+%%% Test rekeying with simultaneous send request
+
+renegotiate1(Config) ->
+ UserDir = proplists:get_value(priv_dir, Config),
+ DataFile = filename:join(UserDir, "renegotiate1.data"),
+
+ Algs = proplists:get_value(preferred_algorithms, Config),
+ {Pid, Host, DPort} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0},
+ {preferred_algorithms,Algs}]),
+
+ {ok,RelayPid,_,RPort} = ssh_relay:start_link({0,0,0,0}, 0, Host, DPort),
+
+ ConnectionRef = ssh_test_lib:std_connect(Config, Host, RPort, [{max_random_length_padding,0}]),
+ {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
+
+ Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
+
+ {ok, Handle} = ssh_sftp:open(SftpPid, DataFile, [write]),
+
+ ok = ssh_sftp:write(SftpPid, Handle, "hi\n"),
+
+ ssh_relay:hold(RelayPid, rx, 20, 1000),
+ ssh_connection_handler:renegotiate(ConnectionRef),
+ spawn(fun() -> ok=ssh_sftp:write(SftpPid, Handle, "another hi\n") end),
+
+ timer:sleep(2000),
+
+ Kex2 = ssh_test_lib:get_kex_init(ConnectionRef),
+
+ false = (Kex2 == Kex1),
+
+ ssh_relay:stop(RelayPid),
+ ssh_sftp:stop_channel(SftpPid),
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid).
+
+%%--------------------------------------------------------------------
+
+%%% Test rekeying with inflight messages from peer
+
+renegotiate2(Config) ->
+ UserDir = proplists:get_value(priv_dir, Config),
+ DataFile = filename:join(UserDir, "renegotiate2.data"),
+
+ Algs = proplists:get_value(preferred_algorithms, Config),
+ {Pid, Host, DPort} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0},
+ {preferred_algorithms,Algs}]),
+
+ {ok,RelayPid,_,RPort} = ssh_relay:start_link({0,0,0,0}, 0, Host, DPort),
+
+ ConnectionRef = ssh_test_lib:std_connect(Config, Host, RPort, [{max_random_length_padding,0}]),
+ {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
+
+ Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
+
+ {ok, Handle} = ssh_sftp:open(SftpPid, DataFile, [write]),
+
+ ok = ssh_sftp:write(SftpPid, Handle, "hi\n"),
+
+ ssh_relay:hold(RelayPid, rx, 20, infinity),
+ spawn(fun() -> ok=ssh_sftp:write(SftpPid, Handle, "another hi\n") end),
+ %% need a small pause here to ensure ssh_sftp:write is executed
+ ct:sleep(10),
+ ssh_connection_handler:renegotiate(ConnectionRef),
+ ssh_relay:release(RelayPid, rx),
+
+ timer:sleep(2000),
+
+ Kex2 = ssh_test_lib:get_kex_init(ConnectionRef),
+
+ false = (Kex2 == Kex1),
+
+ ssh_relay:stop(RelayPid),
+ ssh_sftp:stop_channel(SftpPid),
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid).
+
+%%--------------------------------------------------------------------
+%% Internal functions ------------------------------------------------
+%%--------------------------------------------------------------------
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_dsa b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_dsa
new file mode 100644
index 0000000000..24628e071b
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_dsa
@@ -0,0 +1,12 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBuwIBAAKBgQDIywHurUpOq6kZuMn+XlRzR4hAxF6qwSkuEqkV7iHnLQ0kIwf3
+uAmjFDhuEsQ8653SLxGVvTNp+KFFgDXiLqgM7TPUwDnpbvzEZHPAU+/zPt4sdY2D
+txBfJwT2SFlK6HPOxOcxdDuD+/a59sh8hk/YVOU7ZTcBVsVG8Got4UcF5QIVAPGd
+CPDQKSTlPiM9OwBB1+9p11k5AoGARLxw4l17mET9cU0uf4Ppe5nsCbODJv44ZrSs
+picvypGVLrLcN5KWbm3vjRFCQ5LFunAG3FwLC2Sh0CH6TemoIfRPsRHR7wvpBGdr
+c693UlMOis/mcmvNMQAzuQNW9WrxdzsvWR/r5s6NEHWqKUJGXSPi2d+Ijq/mCOmI
+hzLzyiACgYEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4
+cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+C
+ROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNgCFEjA7wTC
+sQCY/I35vb6GUJn9tEdP
+-----END DSA PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_dsa.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_dsa.pub
new file mode 100644
index 0000000000..018ef6f537
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_dsa.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAMjLAe6tSk6rqRm4yf5eVHNHiEDEXqrBKS4SqRXuIectDSQjB/e4CaMUOG4SxDzrndIvEZW9M2n4oUWANeIuqAztM9TAOelu/MRkc8BT7/M+3ix1jYO3EF8nBPZIWUroc87E5zF0O4P79rn2yHyGT9hU5TtlNwFWxUbwai3hRwXlAAAAFQDxnQjw0Ckk5T4jPTsAQdfvaddZOQAAAIBEvHDiXXuYRP1xTS5/g+l7mewJs4Mm/jhmtKymJy/KkZUustw3kpZube+NEUJDksW6cAbcXAsLZKHQIfpN6agh9E+xEdHvC+kEZ2tzr3dSUw6Kz+Zya80xADO5A1b1avF3Oy9ZH+vmzo0QdaopQkZdI+LZ34iOr+YI6YiHMvPKIAAAAIEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+CROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNg= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa256 b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa256
new file mode 100644
index 0000000000..4b1eb12eaa
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa256
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIJfCaBKIIKhjbJl5F8BedqlXOQYDX5ba9Skypllmx/w+oAoGCCqGSM49
+AwEHoUQDQgAE49RbK2xQ/19ji3uDPM7uT4692LbwWF1TiaA9vUuebMGazoW/98br
+N9xZu0L1AWwtEjs3kmJDTB7eJEGXnjUAcQ==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa256.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa256.pub
new file mode 100644
index 0000000000..a0147e60fa
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa256.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOPUWytsUP9fY4t7gzzO7k+Ovdi28FhdU4mgPb1LnmzBms6Fv/fG6zfcWbtC9QFsLRI7N5JiQ0we3iRBl541AHE= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa384 b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa384
new file mode 100644
index 0000000000..4e8aa40959
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa384
@@ -0,0 +1,6 @@
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDCYXb6OSAZyXRfLXOtMo43za197Hdc/T0YKjgQQjwDt6rlRwqTh7v7S
+PV2kXwNGdWigBwYFK4EEACKhZANiAARN2khlJUOOIiwsWHEALwDieeZR96qL4pUd
+ci7aeGaczdUK5jOA9D9zmBZtSYTfO8Cr7ekVghDlcWAIJ/BXcswgQwSEQ6wyfaTF
+8FYfyr4l3u9IirsnyaFzeIgeoNis8Gw=
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa384.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa384.pub
new file mode 100644
index 0000000000..41e722e545
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa384.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBE3aSGUlQ44iLCxYcQAvAOJ55lH3qovilR1yLtp4ZpzN1QrmM4D0P3OYFm1JhN87wKvt6RWCEOVxYAgn8FdyzCBDBIRDrDJ9pMXwVh/KviXe70iKuyfJoXN4iB6g2KzwbA== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa521 b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa521
new file mode 100644
index 0000000000..7196f46e97
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa521
@@ -0,0 +1,7 @@
+-----BEGIN EC PRIVATE KEY-----
+MIHbAgEBBEFMadoz4ckEcClfqXa2tiUuYkJdDfwq+/iFQcpt8ESuEd26IY/vm47Q
+9UzbPkO4ou8xkNsQ3WvCRQBBWtn5O2kUU6AHBgUrgQQAI6GBiQOBhgAEAde5BRu5
+01/jS0jRk212xsb2DxPrxNpgp6IMCV8TA4Eps+8bSqHB091nLiBcP422HXYfuCd7
+XDjSs8ihcmhp0hCRASLqZR9EzW9W/SOt876May1Huj5X+WSO6RLe7vPn9vmf7kHf
+pip6m7M7qp2qGgQ3q2vRwS2K/O6156ohiOlmuuFs
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa521.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa521.pub
new file mode 100644
index 0000000000..8f059120bc
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa521.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAHXuQUbudNf40tI0ZNtdsbG9g8T68TaYKeiDAlfEwOBKbPvG0qhwdPdZy4gXD+Nth12H7gne1w40rPIoXJoadIQkQEi6mUfRM1vVv0jrfO+jGstR7o+V/lkjukS3u7z5/b5n+5B36YqepuzO6qdqhoEN6tr0cEtivzuteeqIYjpZrrhbA== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed25519 b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed25519
new file mode 100644
index 0000000000..401a3e4a9a
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed25519
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACDm9P8/gC0IOKmwHLSvkmEtS2Xx0RRqUDqC6wY6UgDVnwAAAJg3+6xpN/us
+aQAAAAtzc2gtZWQyNTUxOQAAACDm9P8/gC0IOKmwHLSvkmEtS2Xx0RRqUDqC6wY6UgDVnw
+AAAEBzC/Z2WGJhZ3l3tIBnUc6DCbp+lXY2yc2RRpWQTdf8sub0/z+ALQg4qbActK+SYS1L
+ZfHRFGpQOoLrBjpSANWfAAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed25519.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed25519.pub
new file mode 100644
index 0000000000..a5c03b19c1
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed25519.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOb0/z+ALQg4qbActK+SYS1LZfHRFGpQOoLrBjpSANWf uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed448 b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed448
new file mode 100644
index 0000000000..8ecfd710dc
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed448
@@ -0,0 +1,10 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz
+c2gtZWQ0NDgAAAA53OqeePNaG/NJmoMbELhskKrAHNhLZ6AQm1WjbpMoseNl/OFh
+1xznExpUPqTLX36fHYsAaWRHABQAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA
+ADnc6p5481ob80magxsQuGyQqsAc2EtnoBCbVaNukyix42X84WHXHOcTGlQ+pMtf
+fp8diwBpZEcAFAAAAAByzSPST3FCdOdENDI3uTKQ9RH2Ql+Y5kRZ/yA+iYUIP/32
+BQBVOrwOBc0CGEvbicTM1n4YeVEmfrMo3OqeePNaG/NJmoMbELhskKrAHNhLZ6AQ
+m1WjbpMoseNl/OFh1xznExpUPqTLX36fHYsAaWRHABQAAAAAAAECAwQ=
+-----END OPENSSH PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed448.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed448.pub
new file mode 100644
index 0000000000..cec0765a5d
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed448.pub
@@ -0,0 +1 @@
+ssh-ed448 AAAACXNzaC1lZDQ0OAAAADnc6p5481ob80magxsQuGyQqsAc2EtnoBCbVaNukyix42X84WHXHOcTGlQ+pMtffp8diwBpZEcAFAA= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_rsa b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_rsa
new file mode 100644
index 0000000000..2202c2ead8
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_rsa
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAztjiyj2tdfkji0fewWS0kABg0IABgG20NvL1PnHJLr98we7w
+W7f3j27EGjW/ApuycsWXXKi0L82q8uDicoHHb3JI2JkT70oi0yG1Dx/zwPN+dkA7
+LBT1J3UK2hJTFPhp855CwY/ss9xpBsd1Fv3zuHifEqNGljeg1PjmQ3pNhxA/M0aZ
+cLnfIUdZ5Hr+t+4es3zaWo4tLBKmZu6BkVGQKPGXeMkIAMtJlG24l7qKDRkR5TYA
+ZT7P8Vn7hnuFuCNbrJSm686GawBxTQXom23dg9UcWxoHB7UiHFoR6j0bQAX+4R7b
+IwculRDcvzrgCu6u06oFILwY7MlsxpX9hGTl1wIDAQABAoIBAFeP6pmQeICrYceR
+OhQGLIWVE2bP+VLDnflw6i5v/qlieE6kdm1tOEgorK0nuV9CR81cJdIcvIJL/yTn
+3BR7KdDcwUenrY+rg4h7CWmIrigtK4ilciccDBeS7XAZN8B11GxDv6Cu65XMJU2w
+W7nK8URTE4vRQI1QqS3e26MPAAi/LVOt3ZPI6zg/GHEwnq0IVSQAOndLBr/IWZk5
+SANrkfwX8WS7/UxZgDptT9dyUQ5Pnj5mieTlIvBwyczdhZ7RDa8HdCSHW3xF83V1
+A0pkn6+TRojumYyr4RrPQj6htE64Hgx9w1Dv/UINjPXl5mGlbxQHMWGzlqD/qpyI
+wg7RakECgYEA+9ARZpHfEFz+EEFi8l9J+BtJDo00WaKCOZHh5UJ8W+NreqSd8nSx
+5u6wYwMJjRX2Hwv+FBEhxGbo1+ff6p++cYmiSlDtN2XRCDkBWvvGlxu55BDULrhx
+f8lqaV3XGmOy2rQusp8hiHmkmPJCSVj3oJqQnbqJ2zahXAx1rTPwHqECgYEA0kln
+4h+ZkZ+aldOMGF0d0txTcTqZvsSVKiFTSD9of/fiSDqb6xtLT2+ys6FZoFL9lyK8
+gtqH642CDQ+3WT6Nmn4kMF5HNVpEuCeRDeRhiquWeKaAQDyvZ5ym1+Cn3GhsO7Di
+d2LJKV5hOoN77loVY5nwnUVIJ0h+WLf0T7DTCXcCgYEAiNT7X50MdTvS4splFgcp
+jqRlAn9AXySrVtUqxwVlxhjCIpapLUK0GSTCvEq+OeghIaXGnujgTHUPOaNKTZgY
+SGHdyjxHar7s42b2kZYWx63NSVzLr8eSBTpRlIflhvV+DtGyPmWyNxLCmkmqM2kg
+xii3RL5EgtYgwIAUwdVjOYECgYBRPlsMWfkS8f7fc+PkZdVn6gey71kHAxw+MrHi
+b90H09Vw4nPq2Zi3EAiSrfvanTWsdpcuVw+8See89B16NViwH5wLs+D/E+kI3QCF
+xX6J/NEdu/ZA2zFJbpRnQzyXQyDNzwEv7tKZUQVvfe0boWIyIP99Q48k3jUyQZ/6
+Se6+8QKBgQCXl8H2K3CsZxoujKLb2qoEOPbxJQ2hxoMTS5XuQECReIVsNuptWrur
+DF8WJi/B6AqwRX1P3l56RNwqB1yDBqv0QVLpU7vU/FmWqLWTn0r3AvM74qftvfAE
+oa31wcYoCqPJoKgCG7TThLhNt2v5hL7sVgZNO0ueAiHhJbFLaf7ceg==
+-----END RSA PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_rsa.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_rsa.pub
new file mode 100644
index 0000000000..b4084d320a
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_rsa.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDO2OLKPa11+SOLR97BZLSQAGDQgAGAbbQ28vU+cckuv3zB7vBbt/ePbsQaNb8Cm7JyxZdcqLQvzary4OJygcdvckjYmRPvSiLTIbUPH/PA8352QDssFPUndQraElMU+GnznkLBj+yz3GkGx3UW/fO4eJ8So0aWN6DU+OZDek2HED8zRplwud8hR1nkev637h6zfNpaji0sEqZm7oGRUZAo8Zd4yQgAy0mUbbiXuooNGRHlNgBlPs/xWfuGe4W4I1uslKbrzoZrAHFNBeibbd2D1RxbGgcHtSIcWhHqPRtABf7hHtsjBy6VENy/OuAK7q7TqgUgvBjsyWzGlf2EZOXX uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_dsa_key b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_dsa_key
new file mode 100644
index 0000000000..51ab6fbd88
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_dsa_key
@@ -0,0 +1,13 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBuwIBAAKBgQCClaHzE2ul0gKSUxah5W0W8UiJLy4hXngKEqpaUq9SSdVdY2LK
+wVfKH1gt5iuaf1FfzOhsIC9G/GLnjYttXZc92cv/Gfe3gR+s0ni2++MX+T++mE/Q
+diltXv/Hp27PybS67SmiFW7I+RWnT2OKlMPtw2oUuKeztCe5UWjaj/y5FQIVAPLA
+l9RpiU30Z87NRAHY3NTRaqtrAoGANMRxw8UfdtNVR0CrQj3AgPaXOGE4d+G4Gp4X
+skvnCHycSVAjtYxebUkzUzt5Q6f/IabuLUdge3gXrc8BetvrcKbp+XZgM0/Vj2CF
+Ymmy3in6kzGZq7Fw1sZaku6AOU8vLa5woBT2vAcHLLT1bLAzj7viL048T6MfjrOP
+ef8nHvACgYBhDWFQJ1mf99sg92LalVq1dHLmVXb3PTJDfCO/Gz5NFmj9EZbAtdah
+/XcF3DeRF+eEoz48wQF/ExVxSMIhLdL+o+ElpVhlM7Yii+T7dPhkQfEul6zZXu+U
+ykSTXYUbtsfTNRFQGBW2/GfnEc0mnIxfn9v10NEWMzlq5z9wT9P0CgIVAN4wtL5W
+Lv62jKcdskxNyz2NQoBx
+-----END DSA PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_dsa_key.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_dsa_key.pub
new file mode 100644
index 0000000000..4dbb1305b0
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_dsa_key.pub
@@ -0,0 +1,11 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+AAAAB3NzaC1kc3MAAACBAIKVofMTa6XSApJTFqHlbRbxSIkvLiFeeAoSqlpSr1JJ1V1j
+YsrBV8ofWC3mK5p/UV/M6GwgL0b8YueNi21dlz3Zy/8Z97eBH6zSeLb74xf5P76YT9B2
+KW1e/8enbs/JtLrtKaIVbsj5FadPY4qUw+3DahS4p7O0J7lRaNqP/LkVAAAAFQDywJfU
+aYlN9GfOzUQB2NzU0WqrawAAAIA0xHHDxR9201VHQKtCPcCA9pc4YTh34bganheyS+cI
+fJxJUCO1jF5tSTNTO3lDp/8hpu4tR2B7eBetzwF62+twpun5dmAzT9WPYIViabLeKfqT
+MZmrsXDWxlqS7oA5Ty8trnCgFPa8BwcstPVssDOPu+IvTjxPox+Os495/yce8AAAAIBh
+DWFQJ1mf99sg92LalVq1dHLmVXb3PTJDfCO/Gz5NFmj9EZbAtdah/XcF3DeRF+eEoz48
+wQF/ExVxSMIhLdL+o+ElpVhlM7Yii+T7dPhkQfEul6zZXu+UykSTXYUbtsfTNRFQGBW2
+/GfnEc0mnIxfn9v10NEWMzlq5z9wT9P0Cg==
+---- END SSH2 PUBLIC KEY ----
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key256 b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key256
new file mode 100644
index 0000000000..2979ea88ed
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key256
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIMe4MDoit0t8RzSVPwkCBemQ9fhXL+xnTSAWISw8HNCioAoGCCqGSM49
+AwEHoUQDQgAEo2q7U3P6r0W5WGOLtM78UQtofM9UalEhiZeDdiyylsR/RR17Op0s
+VPGSADLmzzgcucLEKy17j2S+oz42VUJy5A==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key256.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key256.pub
new file mode 100644
index 0000000000..85dc419345
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key256.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKNqu1Nz+q9FuVhji7TO/FELaHzPVGpRIYmXg3YsspbEf0UdezqdLFTxkgAy5s84HLnCxCste49kvqM+NlVCcuQ= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key384 b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key384
new file mode 100644
index 0000000000..fb1a862ded
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key384
@@ -0,0 +1,6 @@
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDArxbDfh3p1okrD9wQw6jJ4d4DdlBPD5GqXE8bIeRJiK41Sh40LgvPw
+mkqEDSXK++CgBwYFK4EEACKhZANiAAScl43Ih2lWTDKrSox5ve5uiTXil4smsup3
+CfS1XPjKxgBAmlfBim8izbdrT0BFdQzz2joduNMtpt61wO4rGs6jm0UP7Kim9PC7
+Hneb/99fIYopdMH5NMnk60zGO1uZ2vc=
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key384.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key384.pub
new file mode 100644
index 0000000000..428d5fb7d7
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key384.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBJyXjciHaVZMMqtKjHm97m6JNeKXiyay6ncJ9LVc+MrGAECaV8GKbyLNt2tPQEV1DPPaOh240y2m3rXA7isazqObRQ/sqKb08Lsed5v/318hiil0wfk0yeTrTMY7W5na9w== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key521 b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key521
new file mode 100644
index 0000000000..3e51ec2ecd
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key521
@@ -0,0 +1,7 @@
+-----BEGIN EC PRIVATE KEY-----
+MIHcAgEBBEIB8O1BFkl2HQjQLRLonEZ97da/h39DMa9/0/hvPZWAI8gUPEQcHxRx
+U7b09p3Zh+EBbMFq8+1ae9ds+ZTxE4WFSvKgBwYFK4EEACOhgYkDgYYABAAlWVjq
+Bzg7Wt4gE6UNb1lRE2cnlmH2L/A5uo6qZRx5lPnSKOxEhxSb/Oay1+9d6KRdrh6/
+vlhd9SHDBhLcAPDvWgBnJIEj92Q3pXX4JtoitL0yl+SvvU+vUh966mzHShHzj8p5
+ccOgPkPNoA70yrpGzkIhPezpZOQdCaOXj/jFqNCTDg==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key521.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key521.pub
new file mode 100644
index 0000000000..017a29f4da
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key521.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAAlWVjqBzg7Wt4gE6UNb1lRE2cnlmH2L/A5uo6qZRx5lPnSKOxEhxSb/Oay1+9d6KRdrh6/vlhd9SHDBhLcAPDvWgBnJIEj92Q3pXX4JtoitL0yl+SvvU+vUh966mzHShHzj8p5ccOgPkPNoA70yrpGzkIhPezpZOQdCaOXj/jFqNCTDg== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed25519_key b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed25519_key
new file mode 100644
index 0000000000..13a8fcf491
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed25519_key
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQAAAJi+h4O7voeD
+uwAAAAtzc2gtZWQyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQ
+AAAEBaOcJfGPNemKc1wPHTCmM4Kwvh6dZ0CqY14UT361UnN0lI66JgZZo72XJ7wGBp+h3W
+TDw/pxXddvaomAIHrIl9AAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed25519_key.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed25519_key.pub
new file mode 100644
index 0000000000..156ef4045c
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed25519_key.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIElI66JgZZo72XJ7wGBp+h3WTDw/pxXddvaomAIHrIl9 uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed448_key b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed448_key
new file mode 100644
index 0000000000..31a7e4e8c3
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed448_key
@@ -0,0 +1,10 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz
+c2gtZWQ0NDgAAAA5X9dEm1m0Yf0s54fsYWrUah2hNCSFpw4fig6nXYDpZ3jt8SR2
+m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA
+ADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHl
+D2zR+hq+r+glYYAAAABybIKlYsuAjRDWMr6JyFE+v2ySnzTd+oyfY8mWDvbjSKNS
+jIo/zC8ETjmj/FuUSS+PAy51SaIAmPlbX9dEm1m0Yf0s54fsYWrUah2hNCSFpw4f
+ig6nXYDpZ3jt8SR2m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAAAAECAwQ=
+-----END OPENSSH PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed448_key.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed448_key.pub
new file mode 100644
index 0000000000..8c390dcb58
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed448_key.pub
@@ -0,0 +1 @@
+ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA=
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_rsa_key b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_rsa_key
new file mode 100644
index 0000000000..79968bdd7d
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_rsa_key
@@ -0,0 +1,16 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDCZX+4FBDwZIh9y/Uxee1VJnEXlowpz2yDKwj8semM4q843337
+zbNfxHmladB1lpz2NqyxI175xMIJuDxogyZdsOxGnFAzAnthR4dqL/RWRWzjaxSB
+6IAO9SPYVVlrpZ+1hsjLW79fwXK/yc8VdhRuWTeQiRgYY2ek8+OKbOqz4QIDAQAB
+AoGANmvJzJO5hkLuvyDZHKfAnGTtpifcR1wtSa9DjdKUyn8vhKF0mIimnbnYQEmW
+NUUb3gXCZLi9PvkpRSVRrASDOZwcjoU/Kvww163vBUVb2cOZfFhyn6o2Sk88Tt++
+udH3hdjpf9i7jTtUkUe+QYPsia+wgvvrmn4QrahLAH86+kECQQDx5gFeXTME3cnW
+WMpFz3PPumduzjqgqMMWEccX4FtQkMX/gyGa5UC7OHFyh0N/gSWvPbRHa8A6YgIt
+n8DO+fh5AkEAzbqX4DOn8NY6xJIi42q7l/2jIA0RkB6P7YugW5NblhqBZ0XDnpA5
+sMt+rz+K07u9XZtxgh1xi7mNfwY6lEAMqQJBAJBEauCKmRj35Z6OyeQku59SPsnY
++SJEREVvSNw2lH9SOKQQ4wPsYlTGbvKtNVZgAcen91L5MmYfeckYE/fdIZECQQCt
+64zxsTnM1I8iFxj/gP/OYlJBikrKt8udWmjaghzvLMEw+T2DExJyb9ZNeT53+UMB
+m6O+B/4xzU/djvp+0hbhAkAemIt+rA5kTmYlFndhpvzkSSM8a2EXsO4XIPgGWCTT
+tQKS/tTly0ADMjN/TVy11+9d6zcqadNVuHXHGtR4W0GR
+-----END RSA PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_rsa_key.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_rsa_key.pub
new file mode 100644
index 0000000000..75d2025c71
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_rsa_key.pub
@@ -0,0 +1,5 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+AAAAB3NzaC1yc2EAAAADAQABAAAAgQDCZX+4FBDwZIh9y/Uxee1VJnEXlowpz2yDKwj8
+semM4q843337zbNfxHmladB1lpz2NqyxI175xMIJuDxogyZdsOxGnFAzAnthR4dqL/RW
+RWzjaxSB6IAO9SPYVVlrpZ+1hsjLW79fwXK/yc8VdhRuWTeQiRgYY2ek8+OKbOqz4Q==
+---- END SSH2 PUBLIC KEY ----
diff --git a/lib/ssh/test/ssh_sftpd_SUITE.erl b/lib/ssh/test/ssh_sftpd_SUITE.erl
index 852801d013..4edc28aa96 100644
--- a/lib/ssh/test/ssh_sftpd_SUITE.erl
+++ b/lib/ssh/test/ssh_sftpd_SUITE.erl
@@ -80,9 +80,9 @@ groups() ->
init_per_suite(Config) ->
?CHECK_CRYPTO(
begin
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_dsa(DataDir, PrivDir),
+ ssh:start(),
+ ct:log("Pub keys setup for: ~p",
+ [ssh_test_lib:setup_all_user_host_keys(Config)]),
%% to make sure we don't use public-key-auth
%% this should be tested by other test suites
UserDir = filename:join(proplists:get_value(priv_dir, Config), nopubkey),
@@ -91,8 +91,6 @@ init_per_suite(Config) ->
end).
end_per_suite(Config) ->
- SysDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:clean_dsa(SysDir),
UserDir = filename:join(proplists:get_value(priv_dir, Config), nopubkey),
file:del_dir(UserDir),
ssh:stop().
diff --git a/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl b/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl
index dbf79e3537..c4fb21f483 100644
--- a/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl
+++ b/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl
@@ -64,15 +64,12 @@ init_per_suite(Config) ->
{ok, FileInfo} = file:read_file_info(FileName),
ok = file:write_file_info(FileName,
FileInfo#file_info{mode = 8#400}),
+ ct:log("Pub keys setup for: ~p",
+ [ssh_test_lib:setup_all_user_host_keys(Config)]),
Config
end).
-end_per_suite(Config) ->
- UserDir = filename:join(proplists:get_value(priv_dir, Config), nopubkey),
- file:del_dir(UserDir),
- SysDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:clean_rsa(SysDir),
- ssh_test_lib:clean_dsa(SysDir),
+end_per_suite(_Config) ->
ok.
%%--------------------------------------------------------------------
@@ -83,10 +80,10 @@ init_per_group(_GroupName, Config) ->
end_per_group(_GroupName, Config) ->
Config.
%%--------------------------------------------------------------------
-
init_per_testcase(TestCase, Config) ->
ssh:start(),
- DataDir = proplists:get_value(data_dir, Config),
+ UserDir = PrivDir = proplists:get_value(priv_dir, Config),
+ SysDir = filename:join(PrivDir,"system"),
Options =
case atom_to_list(TestCase) of
@@ -110,9 +107,10 @@ init_per_testcase(TestCase, Config) ->
[]
end,
- {Sftpd, Host, Port} = ssh_test_lib:daemon([{preferred_algorithms, ssh_transport:supported_algorithms()},
- {system_dir, DataDir},
- {user_dir, DataDir},
+ {Sftpd, Host, Port} = ssh_test_lib:daemon([{preferred_algorithms,
+ ssh_transport:supported_algorithms()},
+ {system_dir, SysDir},
+ {user_dir, UserDir},
{user_passwords, [{?USER,?PASSWD}]}
| Options]),
@@ -120,7 +118,7 @@ init_per_testcase(TestCase, Config) ->
ssh_sftp:start_channel(Host, Port,
[{silently_accept_hosts, true},
{preferred_algorithms, ssh_transport:supported_algorithms()},
- {user_dir, DataDir},
+ {user_dir, UserDir},
{timeout, 30000}]),
TmpConfig = lists:keydelete(sftp, 1, Config),
NewConfig = lists:keydelete(sftpd, 1, TmpConfig),
@@ -178,7 +176,8 @@ quit(Config) when is_list(Config) ->
timer:sleep(5000),
{ok, NewSftp, _Conn} = ssh_sftp:start_channel(Host, Port,
[{silently_accept_hosts, true},
- {preferred_algorithms, ssh_transport:supported_algorithms()},
+ {preferred_algorithms,
+ ssh_transport:supported_algorithms()},
{user_dir, UserDir},
{user, ?USER}, {password, ?PASSWD}]),
diff --git a/lib/ssh/test/ssh_test_lib.erl b/lib/ssh/test/ssh_test_lib.erl
index ebf60bc1d0..f16c0d6278 100644
--- a/lib/ssh/test/ssh_test_lib.erl
+++ b/lib/ssh/test/ssh_test_lib.erl
@@ -294,25 +294,37 @@ rcv_lingering(Timeout) ->
receive_exec_result([]) ->
expected;
receive_exec_result(Msgs) when is_list(Msgs) ->
- ct:log("Expect data! ~p", [Msgs]),
+ ct:log("~p:~p Expect data! ~p", [?MODULE,?FUNCTION_NAME,Msgs]),
receive
Msg ->
- case lists:member(Msg, Msgs) of
+ case lists:member(Msg, Msgs)
+ orelse lists:member({optional,Msg}, Msgs)
+ of
true ->
- ct:log("Collected data ~p", [Msg]),
- receive_exec_result(Msgs--[Msg]);
+ ct:log("~p:~p Collected data ~p", [?MODULE,?FUNCTION_NAME,Msg]),
+ receive_exec_result(Msgs--[Msg,{optional,Msg}]);
false ->
case Msg of
{ssh_cm,_,{data,_,1, Data}} ->
- ct:log("StdErr: ~p~n", [Data]),
+ ct:log("~p:~p StdErr: ~p~n", [?MODULE,?FUNCTION_NAME,Data]),
receive_exec_result(Msgs);
Other ->
- ct:log("Other ~p", [Other]),
+ ct:log("~p:~p Other ~p", [?MODULE,?FUNCTION_NAME,Other]),
{unexpected_msg, Other}
end
end
after
- 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
+ 30000 ->
+ case lists:all(fun(M) ->
+ is_tuple(M) andalso (element(1,M) == optional)
+ end, Msgs)
+ of
+ false ->
+ ct:fail("timeout ~p:~p",[?MODULE,?FUNCTION_NAME]);
+ true ->
+ ct:log("~p:~p Only optional messages expected!~n ~p", [?MODULE,?FUNCTION_NAME,Msgs]),
+ expected
+ end
end;
receive_exec_result(Msg) ->
receive_exec_result([Msg]).
@@ -325,26 +337,11 @@ receive_exec_result_or_fail(Msg) ->
end.
receive_exec_end(ConnectionRef, ChannelId) ->
- Eof = {ssh_cm, ConnectionRef, {eof, ChannelId}},
- ExitStatus = {ssh_cm, ConnectionRef, {exit_status, ChannelId, 0}},
- Closed = {ssh_cm, ConnectionRef,{closed, ChannelId}},
- case receive_exec_result(ExitStatus) of
- {unexpected_msg, Eof} -> %% Open ssh seems to not allways send these messages
- %% in the same order!
- ct:log("2: Collected data ~p", [Eof]),
- case receive_exec_result(ExitStatus) of
- expected ->
- expected = receive_exec_result(Closed);
- {unexpected_msg, Closed} ->
- ct:log("3: Collected data ~p", [Closed])
- end;
- expected ->
- ct:log("4: Collected data ~p", [ExitStatus]),
- expected = receive_exec_result(Eof),
- expected = receive_exec_result(Closed);
- Other ->
- ct:fail({unexpected_msg, Other})
- end.
+ receive_exec_result(
+ [{ssh_cm, ConnectionRef, {eof, ChannelId}},
+ {optional, {ssh_cm, ConnectionRef, {exit_status, ChannelId, 0}}},
+ {ssh_cm, ConnectionRef, {closed, ChannelId}}
+ ]).
receive_exec_result(Data, ConnectionRef, ChannelId) ->
Eof = {ssh_cm, ConnectionRef, {eof, ChannelId}},
@@ -354,21 +351,6 @@ receive_exec_result(Data, ConnectionRef, ChannelId) ->
expected = receive_exec_result(Closed).
-setup_ssh_auth_keys(RSAFile, DSAFile, Dir) ->
- Entries = ssh_file_entry(RSAFile) ++ ssh_file_entry(DSAFile),
- AuthKeys = public_key:ssh_encode(Entries , auth_keys),
- AuthKeysFile = filename:join(Dir, "authorized_keys"),
- file:write_file(AuthKeysFile, AuthKeys).
-
-ssh_file_entry(PubFile) ->
- case file:read_file(PubFile) of
- {ok, Ssh} ->
- [{Key, _}] = public_key:ssh_decode(Ssh, public_key),
- [{Key, [{comment, "Test"}]}];
- _ ->
- []
- end.
-
failfun(_User, {authmethod,none}) ->
ok;
failfun(User, Reason) ->
@@ -378,236 +360,31 @@ hostname() ->
{ok,Host} = inet:gethostname(),
Host.
-known_hosts(BR) ->
- KnownHosts = ssh_file:file_name(user, "known_hosts", []),
- B = KnownHosts ++ "xxx",
- case BR of
- backup ->
- file:rename(KnownHosts, B);
- restore ->
- file:delete(KnownHosts),
- file:rename(B, KnownHosts)
- end.
-
-setup_dsa(DataDir, UserDir) ->
- file:copy(filename:join(DataDir, "id_dsa"), filename:join(UserDir, "id_dsa")),
- System = filename:join(UserDir, "system"),
- file:make_dir(System),
- file:copy(filename:join(DataDir, "ssh_host_dsa_key"), filename:join(System, "ssh_host_dsa_key")),
- file:copy(filename:join(DataDir, "ssh_host_dsa_key.pub"), filename:join(System, "ssh_host_dsa_key.pub")),
-ct:log("DataDir ~p:~n ~p~n~nSystDir ~p:~n ~p~n~nUserDir ~p:~n ~p",[DataDir, file:list_dir(DataDir), System, file:list_dir(System), UserDir, file:list_dir(UserDir)]),
- setup_dsa_known_host(DataDir, UserDir),
- setup_dsa_auth_keys(DataDir, UserDir).
-
-setup_rsa(DataDir, UserDir) ->
- file:copy(filename:join(DataDir, "id_rsa"), filename:join(UserDir, "id_rsa")),
- System = filename:join(UserDir, "system"),
- file:make_dir(System),
- file:copy(filename:join(DataDir, "ssh_host_rsa_key"), filename:join(System, "ssh_host_rsa_key")),
- file:copy(filename:join(DataDir, "ssh_host_rsa_key.pub"), filename:join(System, "ssh_host_rsa_key.pub")),
-ct:log("DataDir ~p:~n ~p~n~nSystDir ~p:~n ~p~n~nUserDir ~p:~n ~p",[DataDir, file:list_dir(DataDir), System, file:list_dir(System), UserDir, file:list_dir(UserDir)]),
- setup_rsa_known_host(DataDir, UserDir),
- setup_rsa_auth_keys(DataDir, UserDir).
-
-setup_ecdsa(Size, DataDir, UserDir) ->
- file:copy(filename:join(DataDir, "id_ecdsa"++Size), filename:join(UserDir, "id_ecdsa")),
- System = filename:join(UserDir, "system"),
- file:make_dir(System),
- file:copy(filename:join(DataDir, "ssh_host_ecdsa_key"++Size), filename:join(System, "ssh_host_ecdsa_key")),
- file:copy(filename:join(DataDir, "ssh_host_ecdsa_key"++Size++".pub"), filename:join(System, "ssh_host_ecdsa_key.pub")),
-ct:log("DataDir ~p:~n ~p~n~nSystDir ~p:~n ~p~n~nUserDir ~p:~n ~p",[DataDir, file:list_dir(DataDir), System, file:list_dir(System), UserDir, file:list_dir(UserDir)]),
- setup_ecdsa_known_host(Size, System, UserDir),
- setup_ecdsa_auth_keys(Size, DataDir, UserDir).
-
-setup_eddsa(Alg, DataDir, UserDir) ->
- {IdPriv, _IdPub, HostPriv, HostPub} =
- case Alg of
- ed25519 -> {"id_ed25519", "id_ed25519.pub", "ssh_host_ed25519_key", "ssh_host_ed25519_key.pub"};
- ed448 -> {"id_ed448", "id_ed448.pub", "ssh_host_ed448_key", "ssh_host_ed448_key.pub"}
- end,
- file:copy(filename:join(DataDir, IdPriv), filename:join(UserDir, IdPriv)),
- System = filename:join(UserDir, "system"),
- file:make_dir(System),
- file:copy(filename:join(DataDir, HostPriv), filename:join(System, HostPriv)),
- file:copy(filename:join(DataDir, HostPub), filename:join(System, HostPub)),
-ct:log("DataDir ~p:~n ~p~n~nSystDir ~p:~n ~p~n~nUserDir ~p:~n ~p",[DataDir, file:list_dir(DataDir), System, file:list_dir(System), UserDir, file:list_dir(UserDir)]),
- setup_eddsa_known_host(HostPub, DataDir, UserDir),
- setup_eddsa_auth_keys(Alg, DataDir, UserDir).
-
-clean_dsa(UserDir) ->
- del_dirs(filename:join(UserDir, "system")),
- file:delete(filename:join(UserDir,"id_dsa")),
- file:delete(filename:join(UserDir,"known_hosts")),
- file:delete(filename:join(UserDir,"authorized_keys")).
-
-clean_rsa(UserDir) ->
- del_dirs(filename:join(UserDir, "system")),
- file:delete(filename:join(UserDir,"id_rsa")),
- file:delete(filename:join(UserDir,"known_hosts")),
- file:delete(filename:join(UserDir,"authorized_keys")).
-
-setup_dsa_pass_phrase(DataDir, UserDir, Phrase) ->
- try
- {ok, KeyBin} = file:read_file(filename:join(DataDir, "id_dsa")),
- setup_pass_phrase(KeyBin, filename:join(UserDir, "id_dsa"), Phrase),
- System = filename:join(UserDir, "system"),
- file:make_dir(System),
- file:copy(filename:join(DataDir, "ssh_host_dsa_key"), filename:join(System, "ssh_host_dsa_key")),
- file:copy(filename:join(DataDir, "ssh_host_dsa_key.pub"), filename:join(System, "ssh_host_dsa_key.pub")),
- setup_dsa_known_host(DataDir, UserDir),
- setup_dsa_auth_keys(DataDir, UserDir)
- of
- _ -> true
- catch
- _:_ -> false
- end.
+del_dirs(Dir) ->
+ del_dir_contents(Dir),
+ file:del_dir(Dir),
+ ok.
-setup_rsa_pass_phrase(DataDir, UserDir, Phrase) ->
- try
- {ok, KeyBin} = file:read_file(filename:join(DataDir, "id_rsa")),
- setup_pass_phrase(KeyBin, filename:join(UserDir, "id_rsa"), Phrase),
- System = filename:join(UserDir, "system"),
- file:make_dir(System),
- file:copy(filename:join(DataDir, "ssh_host_rsa_key"), filename:join(System, "ssh_host_rsa_key")),
- file:copy(filename:join(DataDir, "ssh_host_rsa_key.pub"), filename:join(System, "ssh_host_rsa_key.pub")),
- setup_rsa_known_host(DataDir, UserDir),
- setup_rsa_auth_keys(DataDir, UserDir)
- of
- _ -> true
- catch
- _:_ -> false
- end.
-setup_ecdsa_pass_phrase(Size, DataDir, UserDir, Phrase) ->
- try
- {ok, KeyBin} =
- case file:read_file(F=filename:join(DataDir, "id_ecdsa"++Size)) of
- {error,E} ->
- ct:log("Failed (~p) to read ~p~nFiles: ~p", [E,F,file:list_dir(DataDir)]),
- file:read_file(filename:join(DataDir, "id_ecdsa"));
- Other ->
- Other
- end,
- setup_pass_phrase(KeyBin, filename:join(UserDir, "id_ecdsa"), Phrase),
- System = filename:join(UserDir, "system"),
- file:make_dir(System),
- file:copy(filename:join(DataDir, "ssh_host_ecdsa_key"++Size), filename:join(System, "ssh_host_ecdsa_key")),
- file:copy(filename:join(DataDir, "ssh_host_ecdsa_key"++Size++".pub"), filename:join(System, "ssh_host_ecdsa_key.pub")),
- setup_ecdsa_known_host(Size, System, UserDir),
- setup_ecdsa_auth_keys(Size, DataDir, UserDir)
- of
- _ -> true
- catch
- _:_ -> false
+del_dir_contents(Dir) ->
+ case file:list_dir(Dir) of
+ {ok, Files} ->
+ do_del_files(Dir, Files);
+ _ ->
+ ok
end.
-setup_pass_phrase(KeyBin, OutFile, Phrase) ->
- [{KeyType, _,_} = Entry0] = public_key:pem_decode(KeyBin),
- Key = public_key:pem_entry_decode(Entry0),
- Salt = crypto:strong_rand_bytes(8),
- Entry = public_key:pem_entry_encode(KeyType, Key,
- {{"DES-CBC", Salt}, Phrase}),
- Pem = public_key:pem_encode([Entry]),
- file:write_file(OutFile, Pem).
-
-setup_dsa_known_host(SystemDir, UserDir) ->
- {ok, SshBin} = file:read_file(filename:join(SystemDir, "ssh_host_dsa_key.pub")),
- [{Key, _}] = public_key:ssh_decode(SshBin, public_key),
- setup_known_hosts(Key, UserDir).
-
-setup_rsa_known_host(SystemDir, UserDir) ->
- {ok, SshBin} = file:read_file(filename:join(SystemDir, "ssh_host_rsa_key.pub")),
- [{Key, _}] = public_key:ssh_decode(SshBin, public_key),
- setup_known_hosts(Key, UserDir).
-
-setup_ecdsa_known_host(_Size, SystemDir, UserDir) ->
- {ok, SshBin} = file:read_file(filename:join(SystemDir, "ssh_host_ecdsa_key.pub")),
- [{Key, _}] = public_key:ssh_decode(SshBin, public_key),
- setup_known_hosts(Key, UserDir).
-
-setup_eddsa_known_host(HostPub, SystemDir, UserDir) ->
- {ok, SshBin} = file:read_file(filename:join(SystemDir, HostPub)),
- [{Key, _}] = public_key:ssh_decode(SshBin, public_key),
- setup_known_hosts(Key, UserDir).
-
-setup_known_hosts(Key, UserDir) ->
- {ok, Hostname} = inet:gethostname(),
- {ok, {A, B, C, D}} = inet:getaddr(Hostname, inet),
- IP = lists:concat([A, ".", B, ".", C, ".", D]),
- HostNames = [{hostnames,[Hostname, IP]}],
- KnownHosts = [{Key, HostNames}],
- KnownHostsEnc = public_key:ssh_encode(KnownHosts, known_hosts),
- KHFile = filename:join(UserDir, "known_hosts"),
- file:write_file(KHFile, KnownHostsEnc).
-
-setup_dsa_auth_keys(Dir, UserDir) ->
- {ok, Pem} = file:read_file(filename:join(Dir, "id_dsa")),
- DSA = public_key:pem_entry_decode(hd(public_key:pem_decode(Pem))),
- PKey = DSA#'DSAPrivateKey'.y,
- P = DSA#'DSAPrivateKey'.p,
- Q = DSA#'DSAPrivateKey'.q,
- G = DSA#'DSAPrivateKey'.g,
- Dss = #'Dss-Parms'{p=P, q=Q, g=G},
- setup_auth_keys([{{PKey, Dss}, [{comment, "Test"}]}], UserDir).
-
-setup_rsa_auth_keys(Dir, UserDir) ->
- {ok, Pem} = file:read_file(filename:join(Dir, "id_rsa")),
- RSA = public_key:pem_entry_decode(hd(public_key:pem_decode(Pem))),
- #'RSAPrivateKey'{publicExponent = E, modulus = N} = RSA,
- PKey = #'RSAPublicKey'{publicExponent = E, modulus = N},
- setup_auth_keys([{ PKey, [{comment, "Test"}]}], UserDir).
-
-setup_ecdsa_auth_keys(Size, Dir, UserDir) ->
- {ok, Pem} =
- case file:read_file(F=filename:join(Dir, "id_ecdsa"++Size)) of
- {error,E} ->
- ct:log("Failed (~p) to read ~p~nFiles: ~p", [E,F,file:list_dir(Dir)]),
- file:read_file(filename:join(Dir, "id_ecdsa"));
- Other ->
- Other
- end,
- ECDSA = public_key:pem_entry_decode(hd(public_key:pem_decode(Pem))),
- #'ECPrivateKey'{publicKey = Q,
- parameters = Param = {namedCurve,_Id0}} = ECDSA,
- PKey = #'ECPoint'{point = Q},
- setup_auth_keys([{ {PKey,Param}, [{comment, "Test"}]}], UserDir).
-
-setup_eddsa_auth_keys(Alg, Dir, UserDir) ->
- SshAlg = case Alg of
- ed25519 -> 'ssh-ed25519';
- ed448 -> 'ssh-ed448'
- end,
- {ok, {ed_pri,Alg,Pub,_}} = ssh_file:user_key(SshAlg, [{user_dir,Dir}]),
- setup_auth_keys([{{ed_pub,Alg,Pub}, [{comment, "Test"}]}], UserDir).
-
-setup_auth_keys(Keys, Dir) ->
- AuthKeys = public_key:ssh_encode(Keys, auth_keys),
- AuthKeysFile = filename:join(Dir, "authorized_keys"),
- ok = file:write_file(AuthKeysFile, AuthKeys),
- AuthKeys.
-
-write_auth_keys(Keys, Dir) ->
- AuthKeysFile = filename:join(Dir, "authorized_keys"),
- file:write_file(AuthKeysFile, Keys).
+do_del_files(Dir, Files) ->
+ lists:foreach(fun(File) ->
+ FullPath = filename:join(Dir,File),
+ case filelib:is_dir(FullPath) of
+ true ->
+ del_dirs(FullPath);
+ false ->
+ file:delete(FullPath)
+ end
+ end, Files).
-del_dirs(Dir) ->
- case file:list_dir(Dir) of
- {ok, []} ->
- file:del_dir(Dir);
- {ok, Files} ->
- lists:foreach(fun(File) ->
- FullPath = filename:join(Dir,File),
- case filelib:is_dir(FullPath) of
- true ->
- del_dirs(FullPath),
- file:del_dir(FullPath);
- false ->
- file:delete(FullPath)
- end
- end, Files);
- _ ->
- ok
- end.
openssh_sanity_check(Config) ->
ssh:start(),
@@ -625,34 +402,6 @@ openssh_sanity_check(Config) ->
{skip, Str}
end.
-openssh_supports(ClientOrServer, Tag, Alg) when ClientOrServer == sshc ;
- ClientOrServer == sshd ->
- SSH_algos = ssh_test_lib:default_algorithms(ClientOrServer),
- L = proplists:get_value(Tag, SSH_algos, []),
- lists:member(Alg, L) orelse
- lists:member(Alg, proplists:get_value(client2server, L, [])) orelse
- lists:member(Alg, proplists:get_value(server2client, L, [])).
-
-%%--------------------------------------------------------------------
-%% Check if we have a "newer" ssh client that supports these test cases
-
-ssh_client_supports_Q() ->
- 0 == check_ssh_client_support2(
- ?MODULE:open_port({spawn, "ssh -Q cipher"})
- ).
-
-check_ssh_client_support2(P) ->
- receive
- {P, {data, _A}} ->
- check_ssh_client_support2(P);
- {P, {exit_status, E}} ->
- ct:log("~p:~p exit_status:~n~p",[?MODULE,?LINE,E]),
- E
- after 5000 ->
- ct:log("Openssh command timed out ~n"),
- -1
- end.
-
%%%--------------------------------------------------------------------
%%% Probe a server or a client about algorithm support
@@ -1189,3 +938,132 @@ mk_dir_path(DirPath) ->
%%ct:log("~p:~p return Other ~p ~ts", [?MODULE,?LINE,Other,DirPath]),
Other
end.
+
+%%%----------------------------------------------------------------
+%%% New
+
+setup_all_user_host_keys(Config) ->
+ DataDir = proplists:get_value(data_dir, Config),
+ PrivDir = proplists:get_value(priv_dir, Config),
+ setup_all_user_host_keys(DataDir, PrivDir).
+
+setup_all_user_host_keys(DataDir, PrivDir) ->
+ setup_all_user_host_keys(DataDir, PrivDir, filename:join(PrivDir,"system")).
+
+setup_all_user_host_keys(DataDir, UserDir, SysDir) ->
+ lists:foldl(fun(Alg, OkAlgs) ->
+ try
+ ok = ssh_test_lib:setup_user_key(Alg, DataDir, UserDir),
+ ok = ssh_test_lib:setup_host_key(Alg, DataDir, SysDir)
+ of
+ ok -> [Alg|OkAlgs]
+ catch
+ error:{badmatch, {error,enoent}} ->
+ OkAlgs;
+ C:E:S ->
+ ct:log("Exception in ~p:~p for alg ~p: ~p:~p~n~p",
+ [?MODULE,?FUNCTION_NAME,Alg,C,E,S]),
+ OkAlgs
+ end
+ end, [], ssh_transport:supported_algorithms(public_key)).
+
+
+setup_all_host_keys(Config) ->
+ DataDir = proplists:get_value(data_dir, Config),
+ PrivDir = proplists:get_value(priv_dir, Config),
+ setup_all_host_keys(DataDir, filename:join(PrivDir,"system")).
+
+setup_all_host_keys(DataDir, SysDir) ->
+ lists:foldl(fun(Alg, OkAlgs) ->
+ try
+ ok = ssh_test_lib:setup_host_key(Alg, DataDir, SysDir)
+ of
+ ok -> [Alg|OkAlgs]
+ catch
+ error:{badmatch, {error,enoent}} ->
+ OkAlgs;
+ C:E:S ->
+ ct:log("Exception in ~p:~p for alg ~p: ~p:~p~n~p",
+ [?MODULE,?FUNCTION_NAME,Alg,C,E,S]),
+ OkAlgs
+ end
+ end, [], ssh_transport:supported_algorithms(public_key)).
+
+setup_user_key(SshAlg, DataDir, UserDir) ->
+ file:make_dir(UserDir),
+ %% Copy private user key to user's dir
+ {ok,_} = file:copy(filename:join(DataDir, file_base_name(user_src,SshAlg)),
+ filename:join(UserDir, file_base_name(user,SshAlg))),
+ %% Setup authorized_keys in user's dir
+ {ok,Pub} = file:read_file(filename:join(DataDir, file_base_name(user_src,SshAlg)++".pub")),
+ ok = file:write_file(filename:join(UserDir, "authorized_keys"),
+ io_lib:format("~n~s~n",[Pub]),
+ [append]),
+ ?ct_log_show_file( filename:join(DataDir, file_base_name(user_src,SshAlg)++".pub") ),
+ ?ct_log_show_file( filename:join(UserDir, "authorized_keys") ),
+ ok.
+
+setup_host_key_create_dir(SshAlg, DataDir, BaseDir) ->
+ SysDir = filename:join(BaseDir,"system"),
+ ct:log("~p:~p SshAlg=~p~nDataDir = ~p~nBaseDir = ~p~nSysDir = ~p",[?MODULE,?LINE,SshAlg, DataDir, BaseDir,SysDir]),
+ file:make_dir(SysDir),
+ setup_host_key(SshAlg, DataDir, SysDir),
+ SysDir.
+
+setup_host_key(SshAlg, DataDir, SysDir) ->
+ mk_dir_path(SysDir),
+ %% Copy private host key to system's dir
+ {ok,_} = file:copy(filename:join(DataDir, file_base_name(system_src,SshAlg)),
+ filename:join(SysDir, file_base_name(system,SshAlg))),
+ ?ct_log_show_file( filename:join(SysDir, file_base_name(system,SshAlg)) ),
+ ok.
+
+setup_known_host(SshAlg, DataDir, UserDir) ->
+ {ok,Pub} = file:read_file(filename:join(DataDir, file_base_name(system_src,SshAlg)++".pub")),
+ S = lists:join(" ", lists:reverse(tl(lists:reverse(string:tokens(binary_to_list(Pub), " "))))),
+ ok = file:write_file(filename:join(UserDir, "known_hosts"),
+ io_lib:format("~p~n",[S])),
+ ?ct_log_show_file( filename:join(UserDir, "known_hosts") ),
+ ok.
+
+
+get_addr_str() ->
+ {ok, Hostname} = inet:gethostname(),
+ {ok, {A, B, C, D}} = inet:getaddr(Hostname, inet),
+ IP = lists:concat([A, ".", B, ".", C, ".", D]),
+ lists:concat([Hostname,",",IP]).
+
+
+file_base_name(user, 'ecdsa-sha2-nistp256') -> "id_ecdsa";
+file_base_name(user, 'ecdsa-sha2-nistp384') -> "id_ecdsa";
+file_base_name(user, 'ecdsa-sha2-nistp521') -> "id_ecdsa";
+file_base_name(user, 'rsa-sha2-256' ) -> "id_rsa";
+file_base_name(user, 'rsa-sha2-384' ) -> "id_rsa";
+file_base_name(user, 'rsa-sha2-512' ) -> "id_rsa";
+file_base_name(user, 'ssh-dss' ) -> "id_dsa";
+file_base_name(user, 'ssh-ed25519' ) -> "id_ed25519";
+file_base_name(user, 'ssh-ed448' ) -> "id_ed448";
+file_base_name(user, 'ssh-rsa' ) -> "id_rsa";
+
+file_base_name(user_src, 'ecdsa-sha2-nistp256') -> "id_ecdsa256";
+file_base_name(user_src, 'ecdsa-sha2-nistp384') -> "id_ecdsa384";
+file_base_name(user_src, 'ecdsa-sha2-nistp521') -> "id_ecdsa521";
+file_base_name(user_src, Alg) -> file_base_name(user, Alg);
+
+file_base_name(system, 'ecdsa-sha2-nistp256') -> "ssh_host_ecdsa_key";
+file_base_name(system, 'ecdsa-sha2-nistp384') -> "ssh_host_ecdsa_key";
+file_base_name(system, 'ecdsa-sha2-nistp521') -> "ssh_host_ecdsa_key";
+file_base_name(system, 'rsa-sha2-256' ) -> "ssh_host_rsa_key";
+file_base_name(system, 'rsa-sha2-384' ) -> "ssh_host_rsa_key";
+file_base_name(system, 'rsa-sha2-512' ) -> "ssh_host_rsa_key";
+file_base_name(system, 'ssh-dss' ) -> "ssh_host_dsa_key";
+file_base_name(system, 'ssh-ed25519' ) -> "ssh_host_ed25519_key";
+file_base_name(system, 'ssh-ed448' ) -> "ssh_host_ed448_key";
+file_base_name(system, 'ssh-rsa' ) -> "ssh_host_rsa_key";
+
+file_base_name(system_src, 'ecdsa-sha2-nistp256') -> "ssh_host_ecdsa_key256";
+file_base_name(system_src, 'ecdsa-sha2-nistp384') -> "ssh_host_ecdsa_key384";
+file_base_name(system_src, 'ecdsa-sha2-nistp521') -> "ssh_host_ecdsa_key521";
+file_base_name(system_src, Alg) -> file_base_name(system, Alg).
+
+%%%----------------------------------------------------------------
diff --git a/lib/ssh/test/ssh_test_lib.hrl b/lib/ssh/test/ssh_test_lib.hrl
index b9af2ecb5d..098ee79044 100644
--- a/lib/ssh/test/ssh_test_lib.hrl
+++ b/lib/ssh/test/ssh_test_lib.hrl
@@ -49,3 +49,13 @@
-define(wait_match(Pattern, FunctionCall), ?wait_match(Pattern, FunctionCall, ok) ).
+%%-------------------------------------------------------------------------
+%% Write file into log
+%%-------------------------------------------------------------------------
+
+-define(ct_log_show_file(File),
+ (fun(File__) ->
+ {ok,Contents__} = file:read_file(File__),
+ ct:log("~p:~p Show file~n~s =~n~s~n",
+ [?MODULE,?LINE,File__, Contents__])
+ end)(File)).
diff --git a/lib/ssh/test/ssh_to_openssh_SUITE.erl b/lib/ssh/test/ssh_to_openssh_SUITE.erl
index 5aa3824702..426efbb5e3 100644
--- a/lib/ssh/test/ssh_to_openssh_SUITE.erl
+++ b/lib/ssh/test/ssh_to_openssh_SUITE.erl
@@ -81,10 +81,6 @@ end_per_suite(_Config) ->
ok.
init_per_group(erlang_server, Config) ->
- DataDir = proplists:get_value(data_dir, Config),
- UserDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_dsa_known_host(DataDir, UserDir),
- ssh_test_lib:setup_rsa_known_host(DataDir, UserDir),
Config;
init_per_group(G, Config) when G==tunnel_distro_server ;
G==tunnel_distro_client ->
@@ -102,11 +98,6 @@ init_per_group(erlang_client, Config) ->
init_per_group(_, Config) ->
Config.
-end_per_group(erlang_server, Config) ->
- UserDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:clean_dsa(UserDir),
- ssh_test_lib:clean_rsa(UserDir),
- Config;
end_per_group(_, Config) ->
Config.
@@ -166,10 +157,16 @@ exec_with_io_in_sshc(Config) when is_list(Config) ->
{failfun, fun ssh_test_lib:failfun/2}]),
ct:sleep(500),
+ PrivDir = proplists:get_value(priv_dir, Config),
+ KnownHosts = filename:join(PrivDir, "known_hosts"),
ExecStr = "\"io:read('% ').\"",
Cmd = "echo howdy. | " ++ ssh_test_lib:open_sshc_cmd(Host, Port,
- " -o StrictHostKeyChecking=no"
- " -x", % Disable X forwarding
+ [" -o UserKnownHostsFile=", KnownHosts,
+ " -o CheckHostIP=no"
+ " -o StrictHostKeyChecking=no"
+ " -q"
+ " -x" % Disable X forwarding
+ ],
ExecStr),
ct:pal("Cmd = ~p~n",[Cmd]),
case os:cmd(Cmd) of
@@ -194,9 +191,15 @@ exec_direct_with_io_in_sshc(Config) when is_list(Config) ->
]),
ct:sleep(500),
+ PrivDir = proplists:get_value(priv_dir, Config),
+ KnownHosts = filename:join(PrivDir, "known_hosts"),
Cmd = "echo ciao. | " ++ ssh_test_lib:open_sshc_cmd(Host, Port,
- " -o StrictHostKeyChecking=no"
- " -x", % Disable X forwarding
+ [" -o UserKnownHostsFile=", KnownHosts,
+ " -o CheckHostIP=no"
+ " -o StrictHostKeyChecking=no"
+ " -q"
+ " -x" % Disable X forwarding
+ ],
"'? '"),
ct:pal("Cmd = ~p~n",[Cmd]),
case os:cmd(Cmd) of
@@ -214,7 +217,6 @@ erlang_server_openssh_client_renegotiate(Config) ->
_PubKeyAlg = ssh_rsa,
SystemDir = proplists:get_value(data_dir, Config),
PrivDir = proplists:get_value(priv_dir, Config),
- KnownHosts = filename:join(PrivDir, "known_hosts"),
{Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
{failfun, fun ssh_test_lib:failfun/2}]),
@@ -225,9 +227,13 @@ erlang_server_openssh_client_renegotiate(Config) ->
Data = lists:duplicate(trunc(1.1*RenegLimitK*1024), $a),
ok = file:write_file(DataFile, Data),
+ KnownHosts = filename:join(PrivDir, "known_hosts"),
Cmd = ssh_test_lib:open_sshc_cmd(Host, Port,
[" -o UserKnownHostsFile=", KnownHosts,
- " -o StrictHostKeyChecking=no",
+ " -o CheckHostIP=no"
+ " -o StrictHostKeyChecking=no"
+ " -q"
+ " -x",
" -o RekeyLimit=",integer_to_list(RenegLimitK),"K"]),
@@ -268,7 +274,6 @@ erlang_server_openssh_client_renegotiate(Config) ->
tunnel_out_non_erlclient_erlserver(Config) ->
SystemDir = proplists:get_value(data_dir, Config),
PrivDir = proplists:get_value(priv_dir, Config),
- KnownHosts = filename:join(PrivDir, "known_hosts"),
{_Pid, Host, Port} = ssh_test_lib:daemon([{tcpip_tunnel_out, true},
{system_dir, SystemDir},
@@ -278,9 +283,13 @@ tunnel_out_non_erlclient_erlserver(Config) ->
ListenHost = {127,0,0,1},
ListenPort = 2345,
+ KnownHosts = filename:join(PrivDir, "known_hosts"),
Cmd = ssh_test_lib:open_sshc_cmd(Host, Port,
[" -o UserKnownHostsFile=", KnownHosts,
- " -o StrictHostKeyChecking=no",
+ " -o CheckHostIP=no"
+ " -o StrictHostKeyChecking=no"
+ " -q"
+ " -x",
" -R ",integer_to_list(ListenPort),":127.0.0.1:",integer_to_list(ToPort)]),
spawn(fun() ->
ct:log(["ssh command:\r\n ",Cmd],[]),
@@ -295,7 +304,6 @@ tunnel_out_non_erlclient_erlserver(Config) ->
tunnel_in_non_erlclient_erlserver(Config) ->
SystemDir = proplists:get_value(data_dir, Config),
UserDir = proplists:get_value(priv_dir, Config),
- KnownHosts = filename:join(UserDir, "known_hosts"),
{_Pid, Host, Port} = ssh_test_lib:daemon([{tcpip_tunnel_in, true},
{system_dir, SystemDir},
{failfun, fun ssh_test_lib:failfun/2}]),
@@ -304,10 +312,14 @@ tunnel_in_non_erlclient_erlserver(Config) ->
ListenHost = {127,0,0,1},
ListenPort = 2345,
+ KnownHosts = filename:join(UserDir, "known_hosts"),
Cmd =
ssh_test_lib:open_sshc_cmd(Host, Port,
[" -o UserKnownHostsFile=", KnownHosts,
- " -o StrictHostKeyChecking=no",
+ " -o CheckHostIP=no"
+ " -o StrictHostKeyChecking=no"
+ " -q"
+ " -x",
" -L ",integer_to_list(ListenPort),":127.0.0.1:",integer_to_list(ToPort)]),
spawn(fun() ->
ct:log(["ssh command:\r\n ",Cmd],[]),
@@ -519,22 +531,6 @@ extra_logout() ->
ok
end.
-%%--------------------------------------------------------------------
-%% Check if we have a "newer" ssh client that supports these test cases
-check_ssh_client_support(Config) ->
- case ssh_test_lib:ssh_client_supports_Q() of
- true ->
- ssh:start(),
- Config;
- _ ->
- {skip, "test case not supported by ssh client"}
- end.
-
-comment(AtomList) ->
- ct:comment(
- string:join(lists:map(fun erlang:atom_to_list/1, AtomList),
- ", ")).
-
%%%----------------------------------------------------------------
no_forwarding() ->
%%% Check if the ssh of the OS has tunneling enabled
diff --git a/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ecdsa_key b/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ecdsa_key
new file mode 100644
index 0000000000..2979ea88ed
--- /dev/null
+++ b/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ecdsa_key
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIMe4MDoit0t8RzSVPwkCBemQ9fhXL+xnTSAWISw8HNCioAoGCCqGSM49
+AwEHoUQDQgAEo2q7U3P6r0W5WGOLtM78UQtofM9UalEhiZeDdiyylsR/RR17Op0s
+VPGSADLmzzgcucLEKy17j2S+oz42VUJy5A==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ecdsa_key.pub b/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ecdsa_key.pub
new file mode 100644
index 0000000000..85dc419345
--- /dev/null
+++ b/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ecdsa_key.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKNqu1Nz+q9FuVhji7TO/FELaHzPVGpRIYmXg3YsspbEf0UdezqdLFTxkgAy5s84HLnCxCste49kvqM+NlVCcuQ= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ed25519_key b/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ed25519_key
new file mode 100644
index 0000000000..13a8fcf491
--- /dev/null
+++ b/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ed25519_key
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQAAAJi+h4O7voeD
+uwAAAAtzc2gtZWQyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQ
+AAAEBaOcJfGPNemKc1wPHTCmM4Kwvh6dZ0CqY14UT361UnN0lI66JgZZo72XJ7wGBp+h3W
+TDw/pxXddvaomAIHrIl9AAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ed25519_key.pub b/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ed25519_key.pub
new file mode 100644
index 0000000000..156ef4045c
--- /dev/null
+++ b/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ed25519_key.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIElI66JgZZo72XJ7wGBp+h3WTDw/pxXddvaomAIHrIl9 uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_upgrade_SUITE.erl b/lib/ssh/test/ssh_upgrade_SUITE.erl
index 4417962d26..78657b2014 100644
--- a/lib/ssh/test/ssh_upgrade_SUITE.erl
+++ b/lib/ssh/test/ssh_upgrade_SUITE.erl
@@ -61,9 +61,7 @@ init_per_suite(Config0) ->
end_per_suite(Config) ->
ct_release_test:cleanup(Config),
- ssh:stop(),
- UserDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:clean_rsa(UserDir).
+ ssh:stop().
init_per_testcase(_TestCase, Config) ->
Config.
diff --git a/lib/ssh/vsn.mk b/lib/ssh/vsn.mk
index 70307d6039..732c3f8766 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.9
+SSH_VSN = 4.10
APP_VSN = "ssh-$(SSH_VSN)"
diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml
index 93d58939f2..8eed6a9d1a 100644
--- a/lib/ssl/doc/src/notes.xml
+++ b/lib/ssl/doc/src/notes.xml
@@ -27,6 +27,177 @@
</header>
<p>This document describes the changes made to the SSL application.</p>
+<section><title>SSL 10.0</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <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-16562 Aux Id: ERL-1168 </p>
+ </item>
+ <item>
+ <p>
+ Correct translation of OpenSSL legacy names for two
+ legacy cipher suites</p>
+ <p>
+ Own Id: OTP-16573 Aux Id: ERIERL-477 </p>
+ </item>
+ <item>
+ <p>
+ Correct documentation for PSK identity and SRP username.</p>
+ <p>
+ Own Id: OTP-16585</p>
+ </item>
+ <item>
+ <p>
+ Make sure client hostname check is run when client uses
+ its own verify_fun</p>
+ <p>
+ Own Id: OTP-16626 Aux Id: ERL-1232 </p>
+ </item>
+ <item>
+ <p>
+ Improved signature selection mechanism in TLS 1.3 for
+ increased interoperability.</p>
+ <p>
+ Own Id: OTP-16638 Aux Id: ERL-1206 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Drop support for SSL-3.0. Support for this legacy TLS
+ version has not been enabled by default since OTP 19. Now
+ all code to support it has been removed, that is SSL-3.0
+ protocol version can not be used and is considered
+ invalid.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-14790</p>
+ </item>
+ <item>
+ <p>
+ Added support for RSA-PSS signature schemes</p>
+ <p>
+ Own Id: OTP-15247</p>
+ </item>
+ <item>
+ <p>
+ Improve interoperability by implementing the middlebox
+ compatiblity mode.</p>
+ <p>
+ The middlebox compatibility mode makes the TLS 1.3
+ handshake look more like a TLS 1.2 handshake and
+ increases the chance of successfully establishing TLS 1.3
+ connections through legacy middleboxes.</p>
+ <p>
+ Own Id: OTP-15589</p>
+ </item>
+ <item>
+ <p>
+ Utilize new properties of <seemfa
+ marker="erts:erlang#dist_ctrl_get_data/1"><c>erlang:dist_ctrl_get_data()</c></seemfa>
+ for performance improvement of Erlang distribution over
+ TLS.</p>
+ <p>
+ Own Id: OTP-16127 Aux Id: OTP-15618 </p>
+ </item>
+ <item>
+ <p>
+ Calls of deprecated functions in the <seeguide
+ marker="crypto:new_api#the-old-api">Old Crypto
+ API</seeguide> are replaced by calls of their <seeguide
+ marker="crypto:new_api#the-new-api">substitutions</seeguide>.</p>
+ <p>
+ Own Id: OTP-16346</p>
+ </item>
+ <item>
+ <p>
+ Implement cipher suite TLS_AES_128_CCM_8_SHA256.</p>
+ <p>
+ Own Id: OTP-16391</p>
+ </item>
+ <item>
+ <p>
+ This change adds TLS-1.3 to the list of default supported
+ versions. That is, TLS-1.3 and TLS-1.2 are configured
+ when ssl option 'versions' is not explicitly set.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16400</p>
+ </item>
+ <item>
+ <p>Refactored the internal handling of deprecated and
+ removed functions.</p>
+ <p>
+ Own Id: OTP-16469</p>
+ </item>
+ <item>
+ <p>
+ Extended ssl:versions so that it lists supported,
+ available and implemented TLS/DTLS versions.</p>
+ <p>
+ Own Id: OTP-16519</p>
+ </item>
+ <item>
+ <p>
+ Added new option exclusive for ssl:cipher_suites/2,3</p>
+ <p>
+ Own Id: OTP-16532</p>
+ </item>
+ <item>
+ <p>
+ Avoid DoS attack against stateful session_tickets by
+ making session ticket ids unpredictable.</p>
+ <p>
+ Own Id: OTP-16533</p>
+ </item>
+ <item>
+ <p>
+ Add support for the max_fragment_length extension (RFC
+ 6066).</p>
+ <p>
+ Own Id: OTP-16547 Aux Id: PR-2547 </p>
+ </item>
+ <item>
+ <p>
+ Add srp_username in ssl:connection_info, update the
+ document with types of this function.</p>
+ <p>
+ Own Id: OTP-16584</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SSL 9.6.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix timing bug that could cause ssl sockets to become
+ unresponsive after an ssl:recv/3 call timed out</p>
+ <p>
+ Own Id: OTP-16619 Aux Id: ERL-1213 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>SSL 9.6.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -557,6 +728,22 @@
</section>
+<section><title>SSL 9.2.3.6</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix timing bug that could cause ssl sockets to become
+ unresponsive after an ssl:recv/3 call timed out</p>
+ <p>
+ Own Id: OTP-16619 Aux Id: ERL-1213 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>SSL 9.2.3.5</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 566b5e7eed..1424c795a6 100644
--- a/lib/ssl/doc/src/ssl.xml
+++ b/lib/ssl/doc/src/ssl.xml
@@ -449,8 +449,8 @@
<code>
fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom() |
{revoked, atom()}} |
- {extension, #'Extension'{}}, InitialUserState :: term()) ->
- {valid, UserState :: term()} | {valid_peer, UserState :: term()} |
+ {extension, #'Extension'{}} | valid | valid_peer, InitialUserState :: term()) ->
+ {valid, UserState :: term()} |
{fail, Reason :: term()} | {unknown, UserState :: term()}.
</code>
@@ -658,9 +658,9 @@ fun(Chain::[public_key:der_encoded()]) ->
<desc><p>The lookup fun is to defined as follows:</p>
<code>
-fun(psk, PSKIdentity ::string(), UserState :: term()) ->
+fun(psk, PSKIdentity :: binary(), UserState :: term()) ->
{ok, SharedSecret :: binary()} | error;
-fun(srp, Username :: string(), UserState :: term()) ->
+fun(srp, Username :: binary(), UserState :: term()) ->
{ok, {SRPParams :: srp_param_type(), Salt :: binary(),
DerivedKey :: binary()}} | error.
</code>
@@ -879,6 +879,15 @@ fun(srp, Username :: string(), UserState :: term()) ->
</datatype>
<datatype>
+ <name name="max_fragment_length"/>
+ <desc>
+ <p>Specifies the maximum fragment length the client
+ is prepared to accept from the server.
+ See <url href="http://www.ietf.org/rfc/rfc6066.txt">RFC 6066</url></p>
+ </desc>
+ </datatype>
+
+ <datatype>
<name name="client_psk_identity"/>
<desc>
<p>Specifies the identity the client presents to the server.
@@ -1006,8 +1015,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
</desc>
</datatype>
- <datatype_title>TLS/DTLS OPTION DESCRIPTIONS - SERVER </datatype_title>
-
+ <datatype_title>TLS/DTLS OPTION DESCRIPTIONS - SERVER</datatype_title>
<datatype>
<name name="server_option"/>
@@ -1260,6 +1268,39 @@ fun(srp, Username :: string(), UserState :: term()) ->
</p></note>
</desc>
</datatype>
+
+ <datatype>
+ <name name="connection_info"/>
+ </datatype>
+
+ <datatype>
+ <name name="common_info"/>
+ </datatype>
+
+ <datatype>
+ <name name="curve_info"/>
+ </datatype>
+
+ <datatype>
+ <name name="ssl_options_info"/>
+ </datatype>
+
+ <datatype>
+ <name name="security_info"/>
+ </datatype>
+
+ <datatype>
+ <name name="connection_info_items"/>
+ </datatype>
+
+ <datatype>
+ <name name="connection_info_item"/>
+ </datatype>
+
+ <datatype>
+ <name name="tls_options_name"/>
+ </datatype>
+
</datatypes>
<!--
@@ -1293,21 +1334,31 @@ fun(srp, Username :: string(), UserState :: term()) ->
<func>
<name name="cipher_suites" arity="2" since="OTP 20.3"/>
- <fsummary>Returns a list of all default or
- all supported cipher suites.</fsummary>
- <desc><p>Returns all default or all supported (except anonymous),
- or all anonymous cipher suites for a
- TLS version</p>
-
- <note><p>The cipher suites returned by this function are the
- cipher suites that the OTP ssl application can support provided
- that they are supported by the cryptolib linked with the OTP
- crypto application. Use <seemfa
- marker="#filter_cipher_suites/2"> ssl:filter_cipher_suites(Suites,
- []).</seemfa> to filter the list for the current
- cryptolib. Note that cipher suites may be filtered out because
- they are too old or too new depending on the
- cryptolib</p></note>
+ <fsummary>Returns a list of cipher suites.</fsummary>
+ <desc><p>Lists all possible cipher suites corresponding to
+ <c>Description</c> that are available. The
+ <c>exclusive</c> option will exclusively list cipher suites
+ introduced in <c>Version</c> whereas the the other options
+ are inclusive from the lowest possible version to
+ <c>Version</c>. The <c>all</c> options includes all suites
+ except the anonymous.
+ </p>
+
+ <note><p>TLS-1.3 has no overlapping cipher suites with previous
+ TLS versions, that is the result of
+ <c>cipher_suites(all, 'tlsv1.3').</c> contains a separate set of
+ suites that can be used with TLS-1.3 an other set that can be used
+ if a lower version is negotiated. No anonymous suites are
+ supported by TLS-1.3.</p>
+
+ <p>Also note that the cipher suites returned
+ by this function are the cipher suites that the OTP ssl
+ application can support provided that they are supported by the
+ cryptolib linked with the OTP crypto application. Use <seemfa
+ marker="#filter_cipher_suites/2"> ssl:filter_cipher_suites(Suites,
+ []).</seemfa> to filter the list for the current cryptolib. Note
+ that cipher suites may be filtered out because they are too old or
+ too new depending on the cryptolib</p></note>
</desc>
</func>
@@ -1836,18 +1887,19 @@ fun(srp, Username :: string(), UserState :: term()) ->
<func>
<name since="OTP R14B" name="versions" arity="0" />
- <fsummary>Returns version information relevant for the
- SSL application.</fsummary>
+ <fsummary>Lists information, mainly concerning TLS/DTLS versions,
+ in runtime for debugging and testing purposes.</fsummary>
<desc>
- <p>Returns version information relevant for the SSL
- application.</p>
+ <p>Lists information, mainly concerning TLS/DTLS versions,
+ in runtime for debugging and testing purposes.
+ </p>
<taglist>
<tag><c>app_vsn</c></tag>
<item>The application version of the SSL application.</item>
- <tag><c>default_supported</c></tag>
- <item>TLS versions supported by default if crypto lib
- support is sufficent·
+ <tag><c>supported</c></tag>
+ <item>TLS versions supported with current application environment
+ and crypto library configuration.
Overridden by a version option on
<seemfa marker="#connect/2"> connect/[2,3,4]</seemfa>,
<seemfa marker="#listen/2"> listen/2</seemfa>, and <seemfa
@@ -1856,9 +1908,9 @@ fun(srp, Username :: string(), UserState :: term()) ->
marker="#connection_information/1">connection_information/1
</seemfa>.</item>
- <tag><c>default_supported_dtls</c></tag>
- <item>DTLS versions supported by default if crypto lib
- support is sufficent·
+ <tag><c>supported_dtls</c></tag>
+ <item>DTLS versions supported with current application environment
+ and crypto library configuration.
Overridden by a version option on
<seemfa marker="#connect/2"> connect/[2,3,4]</seemfa>,
<seemfa marker="#listen/2"> listen/2</seemfa>, and <seemfa
@@ -1868,19 +1920,23 @@ fun(srp, Username :: string(), UserState :: term()) ->
</seemfa>.</item>
<tag><c>available</c></tag>
- <item>All TLS versions than can be supported by the SSL application.
+ <item>All TLS versions supported with the
+ linked crypto library.
</item>
<tag><c>available_dtls</c></tag>
- <item>All DTLS versions than can be supported by the SSL application.</item>
+ <item>All DTLS versions supported with the
+ linked crypto library.</item>
- <tag><c>crypto_support</c></tag>
- <item>TLS versions than has sufficient crypto lib
- support through the Crypto applications currently linked crypto lib.</item>
-
- <tag><c>crypto_support_dtls</c></tag>
- <item>All DTLS versions than has sufficient crypto lib
- support through the Crypto applications currently linked crypto lib.</item>
+ <tag><c>implemented</c></tag>
+ <item>All TLS versions supported by the SSL
+ application if linked with a crypto library with the
+ necessary support.</item>
+
+ <tag><c>implemented_dtls</c></tag>
+ <item>All DTLS versions supported by the SSL
+ application if linked with a crypto library with the
+ necessary support.</item>
</taglist>
</desc>
diff --git a/lib/ssl/doc/src/standards_compliance.xml b/lib/ssl/doc/src/standards_compliance.xml
index abbcfc46ce..7a803a6195 100644
--- a/lib/ssl/doc/src/standards_compliance.xml
+++ b/lib/ssl/doc/src/standards_compliance.xml
@@ -133,10 +133,8 @@
<item>Key Exchange: ECDHE</item>
<item>Groups: all standard groups supported for the Diffie-Hellman key exchange</item>
<item>Ciphers: all cipher suites are supported</item>
- <item>Signature Algorithms: rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pkcs1_sha512,
- ecdsa_secp256r1_sha256, ecdsa_secp384r1_sha384, ecdsa_secp521r1_sha512, rsa_pss_rsae_sha256,
- rsa_pss_rsae_sha384, rsa_pss_rsae_sha512, rsa_pkcs1_sha1 and ecdsa_sha1</item>
- <item>Certificates: RSA (it MUST use the rsaEncryption OID) and ECDSA keys</item>
+ <item>Signature Algorithms: All algorithms form RFC 8456</item>
+ <item>Certificates: RSA and ECDSA keys</item>
</list>
<p>Other notable features:</p>
<list type="bulleted">
@@ -180,7 +178,7 @@
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">RSASSA-PSS signature schemes</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>23</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -305,8 +303,8 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">max_fragment_length (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>ssl-@OTP-16547@</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -444,8 +442,8 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">max_fragment_length (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>ssl-@OTP-16547@</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -704,8 +702,8 @@
</url>
</cell>
<cell align="left" valign="middle"><em>Client</em></cell>
- <cell align="left" valign="middle"><em>PC</em></cell>
- <cell align="left" valign="middle"><em>22.1</em></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -776,20 +774,20 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">rsa_pss_pss_sha256</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</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">rsa_pss_pss_sha384</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</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">rsa_pss_pss_sha512</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</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -879,20 +877,20 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">rsa_pss_pss_sha256</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</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">rsa_pss_pss_sha384</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</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">rsa_pss_pss_sha512</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</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -1234,8 +1232,8 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">max_fragment_length (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>ssl-@OTP-16547@</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -1301,8 +1299,8 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">max_fragment_length (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>ssl-@OTP-16547@</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile
index 0b99cdde52..d53b73a747 100644
--- a/lib/ssl/src/Makefile
+++ b/lib/ssl/src/Makefile
@@ -155,7 +155,7 @@ EXTRA_ERLC_FLAGS = +warn_unused_vars
ERL_COMPILE_FLAGS += -I$(ERL_TOP)/lib/kernel/src \
-pz $(EBIN) \
-pz $(ERL_TOP)/lib/public_key/ebin \
- $(EXTRA_ERLC_FLAGS)
+ $(EXTRA_ERLC_FLAGS) -DVSN=\"$(VSN)\"
# ----------------------------------------------------
# Targets
diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl
index 975dc5fc4e..5d2f3a4253 100644
--- a/lib/ssl/src/dtls_connection.erl
+++ b/lib/ssl/src/dtls_connection.erl
@@ -448,9 +448,12 @@ init({call, From}, {start, Timeout},
Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
Session#session.session_id, Renegotiation, Cert),
+ MaxFragEnum = maps:get(max_frag_enum, Hello#client_hello.extensions, undefined),
+ ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0),
Version = Hello#client_hello.client_version,
HelloVersion = dtls_record:hello_version(Version, Versions),
- State1 = prepare_flight(State0#state{connection_env = CEnv#connection_env{negotiated_version = Version}}),
+ 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}}),
State = State2#state{connection_env = CEnv#connection_env{negotiated_version = Version}, %% RequestedVersion
session = Session,
@@ -580,8 +583,9 @@ hello(internal, #server_hello{} = Hello,
handshake_env = #handshake_env{renegotiation = {Renegotiation, _}},
connection_env = #connection_env{negotiated_version = ReqVersion},
connection_states = ConnectionStates0,
+ session = #session{session_id = OldId},
ssl_options = SslOptions} = State) ->
- case dtls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of
+ case dtls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation, OldId) of
#alert{} = Alert ->
handle_own_alert(Alert, ReqVersion, ?FUNCTION_NAME, State);
{Version, NewId, ConnectionStates, ProtoExt, Protocol} ->
@@ -1107,9 +1111,11 @@ send_handshake_flight(#state{static_env = #static_env{socket = Socket,
connection_states = ConnectionStates0,
ssl_options = #{log_level := LogLevel}} = State0,
Epoch) ->
- %% TODO remove hardcoded Max size
+ 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, 1400, Epoch, ConnectionStates0),
+ 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}, []};
@@ -1123,8 +1129,11 @@ send_handshake_flight(#state{static_env = #static_env{socket = Socket,
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, 1400, Epoch, ConnectionStates0),
+ encode_handshake_flight(lists:reverse(Flight0), Version, MaxSize, Epoch, ConnectionStates0),
{EncChangeCipher, ConnectionStates} = encode_change_cipher(ChangeCipher, Version, Epoch, ConnectionStates1),
send(Transport, Socket, [HsBefore, EncChangeCipher]),
@@ -1141,12 +1150,15 @@ send_handshake_flight(#state{static_env = #static_env{socket = Socket,
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, 1400, Epoch-1, ConnectionStates0),
+ 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, 1400, Epoch, ConnectionStates2),
+ 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]),
@@ -1162,10 +1174,13 @@ send_handshake_flight(#state{static_env = #static_env{socket = Socket,
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, 1400, Epoch, ConnectionStates1),
+ 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]),
diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl
index 861b1aacb4..157e3814c2 100644
--- a/lib/ssl/src/dtls_handshake.erl
+++ b/lib/ssl/src/dtls_handshake.erl
@@ -30,7 +30,7 @@
-include("ssl_alert.hrl").
%% Handshake handling
--export([client_hello/7, client_hello/8, cookie/4, hello/4,
+-export([client_hello/7, client_hello/8, cookie/4, hello/5, hello/4,
hello_verify_request/2]).
%% Handshake encoding
@@ -97,15 +97,16 @@ hello(#server_hello{server_version = Version, random = Random,
compression_method = Compression,
session_id = SessionId, extensions = HelloExt},
#{versions := SupportedVersions} = SslOpt,
- ConnectionStates0, Renegotiation) ->
+ ConnectionStates0, Renegotiation, OldId) ->
+ IsNew = ssl_session:is_new(OldId, SessionId),
case dtls_record:is_acceptable_version(Version, SupportedVersions) of
true ->
handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
Compression, HelloExt, SslOpt,
- ConnectionStates0, Renegotiation);
+ ConnectionStates0, Renegotiation, IsNew);
false ->
?ALERT_REC(?FATAL, ?PROTOCOL_VERSION)
- end;
+ end.
hello(#client_hello{client_version = ClientVersion} = Hello,
#{versions := Versions} = SslOpts,
Info, Renegotiation) ->
@@ -212,7 +213,8 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites,
try ssl_handshake:handle_client_hello_extensions(dtls_record, Random, CipherSuites,
HelloExt, dtls_v1:corresponding_tls_version(Version),
SslOpts, Session0,
- ConnectionStates0, Renegotiation) of
+ ConnectionStates0, Renegotiation,
+ Session0#session.is_resumable) of
{Session, ConnectionStates, Protocol, ServerHelloExt} ->
{Version, {Type, Session}, ConnectionStates, Protocol, ServerHelloExt, HashSign}
catch throw:Alert ->
@@ -220,11 +222,11 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites,
end.
handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
- Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation) ->
+ Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation, IsNew) ->
try ssl_handshake:handle_server_hello_extensions(dtls_record, Random, CipherSuite,
Compression, HelloExt,
dtls_v1:corresponding_tls_version(Version),
- SslOpt, ConnectionStates0, Renegotiation) of
+ SslOpt, ConnectionStates0, Renegotiation, IsNew) of
{ConnectionStates, ProtoExt, Protocol} ->
{Version, SessionId, ConnectionStates, ProtoExt, Protocol}
catch throw:Alert ->
diff --git a/lib/ssl/src/dtls_record.erl b/lib/ssl/src/dtls_record.erl
index ee0ce2d22a..16542a8eb3 100644
--- a/lib/ssl/src/dtls_record.erl
+++ b/lib/ssl/src/dtls_record.erl
@@ -215,8 +215,26 @@ encode_change_cipher_spec(Version, Epoch, ConnectionStates) ->
%% Description: Encodes data to send on the ssl-socket.
%%--------------------------------------------------------------------
encode_data(Data, Version, ConnectionStates) ->
- #{epoch := Epoch} = ssl_record:current_connection_state(ConnectionStates, write),
- encode_plain_text(?APPLICATION_DATA, Version, Epoch, Data, ConnectionStates).
+ #{epoch := Epoch, max_fragment_length := MaxFragmentLength}
+ = ssl_record:current_connection_state(ConnectionStates, write),
+ MaxLength = if is_integer(MaxFragmentLength) ->
+ MaxFragmentLength;
+ true ->
+ ?MAX_PLAIN_TEXT_LENGTH
+ end,
+ case iolist_size(Data) of
+ N when N > MaxLength ->
+ Frags = tls_record:split_iovec(erlang:iolist_to_iovec(Data), MaxLength),
+ {RevCipherText, ConnectionStates1} =
+ lists:foldl(fun(Frag, {Acc, CS0}) ->
+ {CipherText, CS1} =
+ encode_plain_text(?APPLICATION_DATA, Version, Epoch, Frag, CS0),
+ {[CipherText|Acc], CS1}
+ end, {[], ConnectionStates}, Frags),
+ {lists:reverse(RevCipherText), ConnectionStates1};
+ _ ->
+ encode_plain_text(?APPLICATION_DATA, Version, Epoch, Data, ConnectionStates)
+ end.
encode_plain_text(Type, Version, Epoch, Data, ConnectionStates) ->
Write0 = get_connection_state_by_epoch(Epoch, ConnectionStates, write),
@@ -393,7 +411,8 @@ initial_connection_state(ConnectionEnd, BeastMitigation) ->
mac_secret => undefined,
secure_renegotiation => undefined,
client_verify_data => undefined,
- server_verify_data => undefined
+ server_verify_data => undefined,
+ max_fragment_length => undefined
}.
get_dtls_records_aux({DataTag, StateName, _, Versions} = Vinfo, <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer),
diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src
index bebf35a20f..f08cb96d17 100644
--- a/lib/ssl/src/ssl.app.src
+++ b/lib/ssl/src/ssl.app.src
@@ -73,5 +73,5 @@
{applications, [crypto, public_key, kernel, stdlib]},
{env, []},
{mod, {ssl_app, []}},
- {runtime_dependencies, ["stdlib-3.5","public_key-1.7.2","kernel-6.0",
+ {runtime_dependencies, ["stdlib-3.5","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 5ba731dd54..7e52cf573d 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -34,6 +34,11 @@
-include("ssl_handshake.hrl").
-include("ssl_srp.hrl").
+%% Needed to make documentation rendering happy
+-ifndef(VSN).
+-define(VSN,"unknown").
+-endif.
+
%% Application handling
-export([start/0,
start/1,
@@ -102,6 +107,9 @@
-deprecated({ssl_accept, '_', "use ssl_handshake/1,2,3 instead"}).
+-deprecated({cipher_suites, 0, "use cipher_suites/2,3 instead"}).
+-deprecated({cipher_suites, 1, "use cipher_suites/2,3 instead"}).
+
-removed([{negotiated_next_protocol,1,
"use ssl:negotiated_protocol/1 instead"}]).
-removed([{connection_info,1,
@@ -344,7 +352,9 @@
-type custom_verify() :: {Verifyfun :: fun(), InitialUserState :: any()}.
-type crl_check() :: boolean() | peer | best_effort.
--type crl_cache_opts() :: [any()].
+-type crl_cache_opts() :: {Module :: atom(),
+ {DbHandle :: internal | term(),
+ Args :: list()}}.
-type handshake_size() :: integer().
-type hibernate_after() :: timeout().
-type root_fun() :: fun().
@@ -385,6 +395,7 @@
{psk_identity, client_psk_identity()} |
{srp_identity, client_srp_identity()} |
{server_name_indication, sni()} |
+ {max_fragment_length, max_fragment_length()} |
{customize_hostname_check, customize_hostname_check()} |
{signature_algs, client_signature_algs()} |
{fallback, fallback()} |
@@ -407,6 +418,7 @@
-type client_srp_identity() :: srp_identity().
-type customize_hostname_check() :: list().
-type sni() :: HostName :: hostname() | disable.
+-type max_fragment_length() :: undefined | 512 | 1024 | 2048 | 4096.
-type client_signature_algs() :: signature_algs().
-type fallback() :: boolean().
-type ssl_imp() :: new | old.
@@ -458,10 +470,37 @@
alpn => app_level_protocol(),
srp => binary(),
next_protocol => app_level_protocol(),
+ max_frag_enum => 1..4,
ec_point_formats => [0..2],
elliptic_curves => [public_key:oid()],
sni => hostname()}. % exported
%% -------------------------------------------------------------------------------------------------------
+-type connection_info() :: [common_info() | curve_info() | ssl_options_info() | security_info()].
+-type common_info() :: {protocol, protocol_version()} |
+ {session_id, session_id()} |
+ {session_resumption, boolean()} |
+ {selected_cipher_suite, erl_cipher_suite()} |
+ {sni_hostname, term()} |
+ {srp_username, term()}.
+-type curve_info() :: {ecc, {named_curve, term()}}.
+-type ssl_options_info() :: tls_option().
+-type security_info() :: {client_random, binary()} |
+ {server_random, binary()} |
+ {master_secret, binary()}.
+-type connection_info_items() :: [connection_info_item()].
+-type connection_info_item() :: protocol |
+ session_id |
+ session_resumption |
+ selected_cipher_suite |
+ sni_hostname |
+ srp_username |
+ ecc |
+ client_random |
+ server_random |
+ master_secret |
+ tls_options_name().
+-type tls_options_name() :: atom().
+%% -------------------------------------------------------------------------------------------------------
%%%--------------------------------------------------------------------
%%% API
@@ -900,9 +939,7 @@ controlling_process(#sslsocket{pid = {Listen,
%%--------------------------------------------------------------------
-spec connection_information(SslSocket) -> {ok, Result} | {error, reason()} when
SslSocket :: sslsocket(),
- Result :: [{OptionName, OptionValue}],
- OptionName :: atom(),
- OptionValue :: any().
+ Result :: connection_info().
%%
%% Description: Return SSL information for the connection
%%--------------------------------------------------------------------
@@ -921,10 +958,8 @@ connection_information(#sslsocket{pid = {dtls,_}}) ->
%%--------------------------------------------------------------------
-spec connection_information(SslSocket, Items) -> {ok, Result} | {error, reason()} when
SslSocket :: sslsocket(),
- Items :: [OptionName],
- Result :: [{OptionName, OptionValue}],
- OptionName :: atom(),
- OptionValue :: any().
+ Items :: connection_info_items(),
+ Result :: connection_info().
%%
%% Description: Return SSL information for the connection
%%--------------------------------------------------------------------
@@ -1010,45 +1045,46 @@ cipher_suites(all) ->
[ssl_cipher_format:suite_legacy(Suite) || Suite <- available_suites(all)].
%%--------------------------------------------------------------------
--spec cipher_suites(Supported, Version) -> ciphers() when
- Supported :: default | all | anonymous,
+-spec cipher_suites(Description, Version) -> ciphers() when
+ Description :: default | all | exclusive | anonymous,
Version :: protocol_version().
%% Description: Returns all default and all supported cipher suites for a
%% TLS/DTLS version
%%--------------------------------------------------------------------
-cipher_suites(Base, Version) when Version == 'tlsv1.3';
+cipher_suites(Description, Version) when Version == 'tlsv1.3';
Version == 'tlsv1.2';
Version == 'tlsv1.1';
Version == tlsv1 ->
- cipher_suites(Base, tls_record:protocol_version(Version));
-cipher_suites(Base, Version) when Version == 'dtlsv1.2';
+ cipher_suites(Description, tls_record:protocol_version(Version));
+cipher_suites(Description, Version) when Version == 'dtlsv1.2';
Version == 'dtlsv1'->
- cipher_suites(Base, dtls_record:protocol_version(Version));
-cipher_suites(Base, Version) ->
- [ssl_cipher_format:suite_bin_to_map(Suite) || Suite <- supported_suites(Base, Version)].
+ cipher_suites(Description, dtls_record:protocol_version(Version));
+cipher_suites(Description, Version) ->
+ [ssl_cipher_format:suite_bin_to_map(Suite) || Suite <- supported_suites(Description, Version)].
%%--------------------------------------------------------------------
--spec cipher_suites(Supported, Version, rfc | openssl) -> [string()] when
- Supported :: default | all | anonymous,
+-spec cipher_suites(Description, Version, rfc | openssl) -> [string()] when
+ Description :: default | all | exclusive | anonymous,
Version :: protocol_version().
%% Description: Returns all default and all supported cipher suites for a
%% TLS/DTLS version
%%--------------------------------------------------------------------
-cipher_suites(Base, Version, StringType) when Version == 'tlsv1.2';
- Version == 'tlsv1.1';
- Version == tlsv1 ->
- cipher_suites(Base, tls_record:protocol_version(Version), StringType);
-cipher_suites(Base, Version, StringType) when Version == 'dtlsv1.2';
+cipher_suites(Description, Version, StringType) when Version == 'tlsv1.3';
+ Version == 'tlsv1.2';
+ Version == 'tlsv1.1';
+ Version == tlsv1 ->
+ cipher_suites(Description, tls_record:protocol_version(Version), StringType);
+cipher_suites(Description, Version, StringType) when Version == 'dtlsv1.2';
Version == 'dtlsv1'->
- cipher_suites(Base, dtls_record:protocol_version(Version), StringType);
-cipher_suites(Base, Version, rfc) ->
- [ssl_cipher_format:suite_map_to_str(ssl_cipher_format:suite_bin_to_map(Suite))
- || Suite <- supported_suites(Base, Version)];
-cipher_suites(Base, Version, openssl) ->
- [ssl_cipher_format:suite_map_to_openssl_str(ssl_cipher_format:suite_bin_to_map(Suite))
- || Suite <- supported_suites(Base, Version)].
+ cipher_suites(Description, dtls_record:protocol_version(Version), StringType);
+cipher_suites(Description, Version, rfc) ->
+ [ssl_cipher_format:suite_map_to_str(ssl_cipher_format:suite_bin_to_map(Suite))
+ || Suite <- supported_suites(Description, Version)];
+cipher_suites(Description, Version, openssl) ->
+ [ssl_cipher_format:suite_map_to_openssl_str(ssl_cipher_format:suite_bin_to_map(Suite))
+ || Suite <- supported_suites(Description, Version)].
%%--------------------------------------------------------------------
-spec filter_cipher_suites(Suites, Filters) -> Ciphers when
@@ -1316,31 +1352,36 @@ sockname(#sslsocket{pid = [Pid| _], fd = {Transport, Socket,_,_}}) when is_pid(P
%%---------------------------------------------------------------
-spec versions() -> [VersionInfo] when
VersionInfo :: {ssl_app, string()} |
- {supported | available, [tls_version()]} |
- {supported_dtls | available_dtls, [dtls_version()]}.
+ {supported | available | implemented, [tls_version()]} |
+ {supported_dtls | available_dtls | implemented_dtls, [dtls_version()]}.
%%
%% Description: Returns a list of relevant versions.
%%--------------------------------------------------------------------
versions() ->
- TLSVsns = tls_record:supported_protocol_versions(),
- DTLSVsns = dtls_record:supported_protocol_versions(),
- SupportedTLSVsns = [tls_record:protocol_version(Vsn) || Vsn <- TLSVsns],
- SupportedDTLSVsns = [dtls_record:protocol_version(Vsn) || Vsn <- DTLSVsns],
- AvailableTLSVsns = ?ALL_AVAILABLE_VERSIONS,
- AvailableDTLSVsns = ?ALL_AVAILABLE_DATAGRAM_VERSIONS,
- CryptoSupVersionsTLS = [Vsn || Vsn <- AvailableTLSVsns,
- tls_record:sufficient_crypto_support(Vsn)],
- CryptoSupVersionsDTLS = [Vsn || Vsn <- AvailableDTLSVsns,
- tls_record:sufficient_crypto_support(tls_record:protocol_version(
- dtls_v1:corresponding_tls_version(
- dtls_record:protocol_version(Vsn))))],
-
- [{ssl_app, "9.2"}, {default_supported, SupportedTLSVsns},
- {default_supported_dtls, SupportedDTLSVsns},
+ ConfTLSVsns = tls_record:supported_protocol_versions(),
+ ConfDTLSVsns = dtls_record:supported_protocol_versions(),
+ ImplementedTLSVsns = ?ALL_AVAILABLE_VERSIONS,
+ ImplementedDTLSVsns = ?ALL_AVAILABLE_DATAGRAM_VERSIONS,
+
+ TLSCryptoSupported = fun(Vsn) ->
+ tls_record:sufficient_crypto_support(Vsn)
+ end,
+ DTLSCryptoSupported = fun(Vsn) ->
+ tls_record:sufficient_crypto_support(dtls_v1:corresponding_tls_version(Vsn))
+ end,
+ SupportedTLSVsns = [tls_record:protocol_version(Vsn) || Vsn <- ConfTLSVsns, TLSCryptoSupported(Vsn)],
+ SupportedDTLSVsns = [dtls_record:protocol_version(Vsn) || Vsn <- ConfDTLSVsns, DTLSCryptoSupported(Vsn)],
+
+ AvailableTLSVsns = [Vsn || Vsn <- ImplementedTLSVsns, TLSCryptoSupported(tls_record:protocol_version(Vsn))],
+ AvailableDTLSVsns = [Vsn || Vsn <- ImplementedDTLSVsns, DTLSCryptoSupported(dtls_record:protocol_version(Vsn))],
+
+ [{ssl_app, ?VSN},
+ {supported, SupportedTLSVsns},
+ {supported_dtls, SupportedDTLSVsns},
{available, AvailableTLSVsns},
{available_dtls, AvailableDTLSVsns},
- {crypto_support, CryptoSupVersionsTLS},
- {crypto_support_dtls, CryptoSupVersionsDTLS}
+ {implemented, ImplementedTLSVsns},
+ {implemented_dtls, ImplementedDTLSVsns}
].
%%---------------------------------------------------------------
@@ -1509,6 +1550,8 @@ available_suites(all) ->
Version = tls_record:highest_protocol_version([]),
ssl_cipher:filter_suites(ssl_cipher:all_suites(Version)).
+supported_suites(exclusive, {3,Minor}) ->
+ tls_v1:exclusive_suites(Minor);
supported_suites(default, Version) ->
ssl_cipher:suites(Version);
supported_suites(all, Version) ->
@@ -1600,7 +1643,9 @@ handle_option(anti_replay = Option, unbound, OptionsMap, #{rules := Rules}) ->
Value = validate_option(Option, default_value(Option, Rules)),
OptionsMap#{Option => Value};
handle_option(anti_replay = Option, Value0,
- #{session_tickets := SessionTickets} = OptionsMap, #{rules := Rules}) ->
+ #{session_tickets := SessionTickets,
+ versions := Versions} = OptionsMap, #{rules := Rules}) ->
+ assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
assert_option_dependency(Option, session_tickets, [SessionTickets], [stateless]),
case SessionTickets of
stateless ->
@@ -1609,6 +1654,13 @@ handle_option(anti_replay = Option, Value0,
_ ->
OptionsMap#{Option => default_value(Option, Rules)}
end;
+handle_option(beast_mitigation = Option, unbound, OptionsMap, #{rules := Rules}) ->
+ Value = validate_option(Option, default_value(Option, Rules)),
+ OptionsMap#{Option => Value};
+handle_option(beast_mitigation = Option, Value0, #{versions := Versions} = OptionsMap, _Env) ->
+ assert_option_dependency(Option, versions, Versions, ['tlsv1']),
+ Value = validate_option(Option, Value0),
+ OptionsMap#{Option => Value};
handle_option(cacertfile = Option, unbound, #{cacerts := CaCerts,
verify := Verify,
verify_fun := VerifyFun} = OptionsMap, _Env)
@@ -1627,17 +1679,20 @@ handle_option(cacertfile = Option, unbound, #{cacerts := CaCerts,
handle_option(cacertfile = Option, Value0, OptionsMap, _Env) ->
Value = validate_option(Option, Value0),
OptionsMap#{Option => Value};
-handle_option(ciphers = Option, unbound, #{versions := [HighestVersion|_]} = OptionsMap, #{rules := Rules}) ->
- Value = handle_cipher_option(default_value(Option, Rules), HighestVersion),
+handle_option(ciphers = Option, unbound, #{versions := Versions} = OptionsMap, #{rules := Rules}) ->
+ Value = handle_cipher_option(default_value(Option, Rules), Versions),
OptionsMap#{Option => Value};
-handle_option(ciphers = Option, Value0, #{versions := [HighestVersion|_]} = OptionsMap, _Env) ->
- Value = handle_cipher_option(Value0, HighestVersion),
+handle_option(ciphers = Option, Value0, #{versions := Versions} = OptionsMap, _Env) ->
+ Value = handle_cipher_option(Value0, Versions),
OptionsMap#{Option => Value};
handle_option(client_renegotiation = Option, unbound, OptionsMap, #{role := Role}) ->
Value = default_option_role(server, true, Role),
OptionsMap#{Option => Value};
-handle_option(client_renegotiation = Option, Value0, OptionsMap, #{role := Role}) ->
+handle_option(client_renegotiation = Option, Value0,
+ #{versions := Versions} = OptionsMap, #{role := Role}) ->
assert_role(server_only, Role, Option, Value0),
+ assert_option_dependency(Option, versions, Versions,
+ ['tlsv1','tlsv1.1','tlsv1.2']),
Value = validate_option(Option, Value0),
OptionsMap#{Option => Value};
handle_option(eccs = Option, unbound, #{versions := [HighestVersion|_]} = OptionsMap, #{rules := _Rules}) ->
@@ -1677,13 +1732,50 @@ handle_option(key_update_at = Option, Value0, #{versions := Versions} = OptionsM
assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
Value = validate_option(Option, Value0),
OptionsMap#{Option => Value};
+handle_option(next_protocols_advertised = Option, unbound, OptionsMap,
+ #{rules := Rules}) ->
+ Value = validate_option(Option, default_value(Option, Rules)),
+ OptionsMap#{Option => Value};
+handle_option(next_protocols_advertised = Option, Value0,
+ #{versions := Versions} = OptionsMap, _Env) ->
+ assert_option_dependency(next_protocols_advertised, versions, Versions,
+ ['tlsv1','tlsv1.1','tlsv1.2']),
+ Value = validate_option(Option, Value0),
+ OptionsMap#{Option => Value};
handle_option(next_protocol_selector = Option, unbound, OptionsMap, #{rules := Rules}) ->
Value = default_value(Option, Rules),
OptionsMap#{Option => Value};
-handle_option(next_protocol_selector = Option, Value0, OptionsMap, _Env) ->
+handle_option(next_protocol_selector = Option, Value0,
+ #{versions := Versions} = OptionsMap, _Env) ->
+ assert_option_dependency(client_preferred_next_protocols, versions, Versions,
+ ['tlsv1','tlsv1.1','tlsv1.2']),
Value = make_next_protocol_selector(
validate_option(client_preferred_next_protocols, Value0)),
OptionsMap#{Option => Value};
+handle_option(padding_check = Option, unbound, OptionsMap, #{rules := Rules}) ->
+ Value = validate_option(Option, default_value(Option, Rules)),
+ OptionsMap#{Option => Value};
+handle_option(padding_check = Option, Value0, #{versions := Versions} = OptionsMap, _Env) ->
+ assert_option_dependency(Option, versions, Versions, ['tlsv1']),
+ Value = validate_option(Option, Value0),
+ OptionsMap#{Option => Value};
+handle_option(psk_identity = Option, unbound, OptionsMap, #{rules := Rules}) ->
+ Value = validate_option(Option, default_value(Option, Rules)),
+ OptionsMap#{Option => Value};
+handle_option(psk_identity = Option, Value0, #{versions := Versions} = OptionsMap, _Env) ->
+ assert_option_dependency(Option, versions, Versions,
+ ['tlsv1','tlsv1.1','tlsv1.2']),
+ Value = validate_option(Option, Value0),
+ OptionsMap#{Option => Value};
+handle_option(secure_renegotiate = Option, unbound, OptionsMap, #{rules := Rules}) ->
+ Value = validate_option(Option, default_value(Option, Rules)),
+ OptionsMap#{Option => Value};
+handle_option(secure_renegotiate= Option, Value0,
+ #{versions := Versions} = OptionsMap, _Env) ->
+ assert_option_dependency(secure_renegotiate, versions, Versions,
+ ['tlsv1','tlsv1.1','tlsv1.2']),
+ Value = validate_option(Option, Value0),
+ OptionsMap#{Option => Value};
handle_option(reuse_session = Option, unbound, OptionsMap, #{role := Role}) ->
Value =
case Role of
@@ -1693,14 +1785,20 @@ handle_option(reuse_session = Option, unbound, OptionsMap, #{role := Role}) ->
fun(_, _, _, _) -> true end
end,
OptionsMap#{Option => Value};
-handle_option(reuse_session = Option, Value0, OptionsMap, _Env) ->
+handle_option(reuse_session = Option, Value0,
+ #{versions := Versions} = OptionsMap, _Env) ->
+ assert_option_dependency(reuse_session, versions, Versions,
+ ['tlsv1','tlsv1.1','tlsv1.2']),
Value = validate_option(Option, Value0),
OptionsMap#{Option => Value};
%% TODO: validate based on role
handle_option(reuse_sessions = Option, unbound, OptionsMap, #{rules := Rules}) ->
Value = validate_option(Option, default_value(Option, Rules)),
OptionsMap#{Option => Value};
-handle_option(reuse_sessions = Option, Value0, OptionsMap, _Env) ->
+handle_option(reuse_sessions = Option, Value0,
+ #{versions := Versions} = OptionsMap, _Env) ->
+ assert_option_dependency(reuse_sessions, versions, Versions,
+ ['tlsv1','tlsv1.1','tlsv1.2']),
Value = validate_option(Option, Value0),
OptionsMap#{Option => Value};
handle_option(server_name_indication = Option, unbound, OptionsMap, #{host := Host,
@@ -1754,25 +1852,48 @@ handle_option(sni_fun = Option, Value0, OptionsMap, _Env) ->
throw({error, {conflict_options, [sni_fun, sni_hosts]}})
end,
OptionsMap#{Option => Value};
+handle_option(srp_identity = Option, unbound, OptionsMap, #{rules := Rules}) ->
+ Value = validate_option(Option, default_value(Option, Rules)),
+ OptionsMap#{Option => Value};
+handle_option(srp_identity = Option, Value0,
+ #{versions := Versions} = OptionsMap, _Env) ->
+ assert_option_dependency(srp_identity, versions, Versions,
+ ['tlsv1','tlsv1.1','tlsv1.2']),
+ Value = validate_option(Option, Value0),
+ OptionsMap#{Option => Value};
handle_option(supported_groups = Option, unbound, #{versions := [HighestVersion|_]} = OptionsMap, #{rules := _Rules}) ->
Value = handle_supported_groups_option(groups(default), HighestVersion),
OptionsMap#{Option => Value};
-handle_option(supported_groups = Option, Value0, #{versions := [HighestVersion|_]} = OptionsMap, _Env) ->
+handle_option(supported_groups = Option, Value0,
+ #{versions := [HighestVersion|_] = Versions} = OptionsMap, _Env) ->
+ assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
Value = handle_supported_groups_option(Value0, HighestVersion),
OptionsMap#{Option => Value};
+handle_option(use_ticket = Option, unbound, OptionsMap, #{rules := Rules}) ->
+ Value = validate_option(Option, default_value(Option, Rules)),
+ OptionsMap#{Option => Value};
+handle_option(use_ticket = Option, Value0,
+ #{versions := Versions} = OptionsMap, _Env) ->
+ assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
+ Value = validate_option(Option, Value0),
+ OptionsMap#{Option => Value};
+handle_option(user_lookup_fun = Option, unbound, OptionsMap, #{rules := Rules}) ->
+ Value = validate_option(Option, default_value(Option, Rules)),
+ OptionsMap#{Option => Value};
+handle_option(user_lookup_fun = Option, Value0,
+ #{versions := Versions} = OptionsMap, _Env) ->
+ assert_option_dependency(Option, versions, Versions, ['tlsv1','tlsv1.1','tlsv1.2']),
+ Value = validate_option(Option, Value0),
+ OptionsMap#{Option => Value};
handle_option(verify = Option, unbound, OptionsMap, #{rules := Rules}) ->
handle_verify_option(default_value(Option, Rules), OptionsMap);
handle_option(verify = _Option, Value, OptionsMap, _Env) ->
handle_verify_option(Value, OptionsMap);
-
handle_option(verify_fun = Option, unbound, #{verify := Verify} = OptionsMap, #{rules := Rules})
- when Verify =:= verify_none orelse
- Verify =:= 0 ->
+ when Verify =:= verify_none ->
OptionsMap#{Option => default_value(Option, Rules)};
handle_option(verify_fun = Option, unbound, #{verify := Verify} = OptionsMap, _Env)
- when Verify =:= verify_peer orelse
- Verify =:= 1 orelse
- Verify =:= 2 ->
+ when Verify =:= verify_peer ->
OptionsMap#{Option => undefined};
handle_option(verify_fun = Option, Value0, OptionsMap, _Env) ->
Value = validate_option(Option, Value0),
@@ -1923,27 +2044,39 @@ assert_role_value(server, Option, Value, ServerValues, _) ->
throw({error, {options, role, {Option, {Value, {server, ServerValues}}}}})
end.
-
assert_option_dependency(Option, OptionDep, Values0, AllowedValues) ->
- %% special handling for version
- Values =
- case OptionDep of
- versions ->
- lists:map(fun tls_record:protocol_version/1, Values0);
- _ ->
- Values0
- end,
- Set1 = sets:from_list(Values),
- Set2 = sets:from_list(AllowedValues),
- case sets:size(sets:intersection(Set1, Set2)) > 0 of
+ case is_dtls_configured(Values0) of
true ->
+ %% TODO: Check option dependency for DTLS
ok;
false ->
- %% Message = build_error_message(Option, OptionDep, AllowedValues),
- %% throw({error, {options, Message}})
- throw({error, {options, dependency, {Option, {OptionDep, AllowedValues}}}})
+ %% special handling for version
+ Values =
+ case OptionDep of
+ versions ->
+ lists:map(fun tls_record:protocol_version/1, Values0);
+ _ ->
+ Values0
+ end,
+ Set1 = sets:from_list(Values),
+ Set2 = sets:from_list(AllowedValues),
+ case sets:size(sets:intersection(Set1, Set2)) > 0 of
+ true ->
+ ok;
+ false ->
+ throw({error, {options, dependency,
+ {Option, {OptionDep, AllowedValues}}}})
+ end
end.
+is_dtls_configured(Versions) ->
+ Fun = fun (Version) when Version =:= {254, 253} orelse
+ Version =:= {254, 255} ->
+ true;
+ (_) ->
+ false
+ end,
+ lists:any(Fun, Versions).
validate_option(versions, Versions) ->
validate_versions(Versions, Versions);
@@ -1974,8 +2107,6 @@ validate_option(partial_chain, Value) when is_function(Value) ->
Value;
validate_option(fail_if_no_peer_cert, Value) when is_boolean(Value) ->
Value;
-validate_option(verify_client_once, Value) when is_boolean(Value) ->
- Value;
validate_option(depth, Value) when is_integer(Value),
Value >= 0, Value =< 255->
Value;
@@ -2136,6 +2267,13 @@ validate_option(server_name_indication, undefined) ->
validate_option(server_name_indication, disable) ->
disable;
+%% RFC 6066, Section 4
+validate_option(max_fragment_length, I) when I == ?MAX_FRAGMENT_LENGTH_BYTES_1; I == ?MAX_FRAGMENT_LENGTH_BYTES_2;
+ I == ?MAX_FRAGMENT_LENGTH_BYTES_3; I == ?MAX_FRAGMENT_LENGTH_BYTES_4 ->
+ I;
+validate_option(max_fragment_length, undefined) ->
+ undefined;
+
validate_option(sni_hosts, []) ->
[];
validate_option(sni_hosts, [{Hostname, SSLOptions} | Tail]) when is_list(Hostname) ->
@@ -2365,8 +2503,8 @@ emulated_options(Protocol, Opts) ->
dtls_socket:emulated_options(Opts)
end.
-handle_cipher_option(Value, Version) when is_list(Value) ->
- try binary_cipher_suites(Version, Value) of
+handle_cipher_option(Value, Versions) when is_list(Value) ->
+ try binary_cipher_suites(Versions, Value) of
Suites ->
Suites
catch
@@ -2376,37 +2514,44 @@ handle_cipher_option(Value, Version) when is_list(Value) ->
throw({error, {options, {ciphers, Value}}})
end.
-binary_cipher_suites(Version, []) ->
+binary_cipher_suites([{3,4} = Version], []) ->
+ %% Defaults to all supported suites that does
+ %% not require explicit configuration TLS-1.3
+ %% only mode.
+ default_binary_suites(exclusive, Version);
+binary_cipher_suites([Version| _], []) ->
%% Defaults to all supported suites that does
%% not require explicit configuration
- default_binary_suites(Version);
-binary_cipher_suites(Version, [Map|_] = Ciphers0) when is_map(Map) ->
+ default_binary_suites(default, Version);
+binary_cipher_suites(Versions, [Map|_] = Ciphers0) when is_map(Map) ->
Ciphers = [ssl_cipher_format:suite_map_to_bin(C) || C <- Ciphers0],
- binary_cipher_suites(Version, Ciphers);
-binary_cipher_suites(Version, [Tuple|_] = Ciphers0) when is_tuple(Tuple) ->
+ binary_cipher_suites(Versions, Ciphers);
+binary_cipher_suites(Versions, [Tuple|_] = Ciphers0) when is_tuple(Tuple) ->
Ciphers = [ssl_cipher_format:suite_map_to_bin(tuple_to_map(C)) || C <- Ciphers0],
- binary_cipher_suites(Version, Ciphers);
-binary_cipher_suites(Version, [Cipher0 | _] = Ciphers0) when is_binary(Cipher0) ->
+ binary_cipher_suites(Versions, Ciphers);
+binary_cipher_suites([Version |_] = Versions, [Cipher0 | _] = Ciphers0) when is_binary(Cipher0) ->
All = ssl_cipher:all_suites(Version) ++
ssl_cipher:anonymous_suites(Version),
case [Cipher || Cipher <- Ciphers0, lists:member(Cipher, All)] of
[] ->
%% Defaults to all supported suites that does
%% not require explicit configuration
- default_binary_suites(Version);
+ binary_cipher_suites(Versions, []);
Ciphers ->
Ciphers
end;
-binary_cipher_suites(Version, [Head | _] = Ciphers0) when is_list(Head) ->
+binary_cipher_suites(Versions, [Head | _] = Ciphers0) when is_list(Head) ->
%% Format: ["RC4-SHA","RC4-MD5"]
Ciphers = [ssl_cipher_format:suite_openssl_str_to_map(C) || C <- Ciphers0],
- binary_cipher_suites(Version, Ciphers);
-binary_cipher_suites(Version, Ciphers0) ->
+ binary_cipher_suites(Versions, Ciphers);
+binary_cipher_suites(Versions, Ciphers0) ->
%% Format: "RC4-SHA:RC4-MD5"
Ciphers = [ssl_cipher_format:suite_openssl_str_to_map(C) || C <- string:lexemes(Ciphers0, ":")],
- binary_cipher_suites(Version, Ciphers).
+ binary_cipher_suites(Versions, Ciphers).
-default_binary_suites(Version) ->
+default_binary_suites(exclusive, {_, Minor}) ->
+ ssl_cipher:filter_suites(tls_v1:exclusive_suites(Minor));
+default_binary_suites(default, Version) ->
ssl_cipher:filter_suites(ssl_cipher:suites(Version)).
tuple_to_map({Kex, Cipher, Mac}) ->
@@ -2541,19 +2686,14 @@ assert_proplist([Value | _]) ->
throw({option_not_a_key_value_tuple, Value}).
-handle_verify_option(verify_none, #{fail_if_no_peer_cert := _FailIfNoPeerCert} = OptionsMap) ->
- OptionsMap#{verify => verify_none,
- fail_if_no_peer_cert => false};
-handle_verify_option(verify_peer, #{fail_if_no_peer_cert := FailIfNoPeerCert} = OptionsMap) ->
- OptionsMap#{verify => verify_peer,
- fail_if_no_peer_cert => FailIfNoPeerCert};
-%% Handle 0, 1, 2 for backwards compatibility
-handle_verify_option(0, OptionsMap) ->
- handle_verify_option(verify_none, OptionsMap);
-handle_verify_option(1, OptionsMap) ->
- handle_verify_option(verify_peer, OptionsMap#{fail_if_no_peer_cert => false});
-handle_verify_option(2, OptionsMap) ->
- handle_verify_option(verify_peer, OptionsMap#{fail_if_no_peer_cert => true});
+handle_verify_option(verify_none, #{fail_if_no_peer_cert := false} = OptionsMap) ->
+ OptionsMap#{verify => verify_none};
+handle_verify_option(verify_none, #{fail_if_no_peer_cert := true}) ->
+ throw({error, {options, incompatible,
+ {verify, verify_none},
+ {fail_if_no_peer_cert, true}}});
+handle_verify_option(verify_peer, OptionsMap) ->
+ OptionsMap#{verify => verify_peer};
handle_verify_option(Value, _) ->
throw({error, {options, {verify, Value}}}).
diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl
index 6d718dfef9..ade1d396cd 100644
--- a/lib/ssl/src/ssl_certificate.erl
+++ b/lib/ssl/src/ssl_certificate.erl
@@ -133,7 +133,7 @@ file_to_crls(File, DbHandle) ->
[Bin || {'CertificateList', Bin, not_encrypted} <- List].
%%--------------------------------------------------------------------
--spec validate(term(), {extension, #'Extension'{}} | {bad_cert, atom()} | valid,
+-spec validate(term(), {extension, #'Extension'{}} | {bad_cert, atom()} | valid | valid_peer,
term()) -> {valid, term()} |
{fail, tuple()} |
{unknown, term()}.
@@ -321,6 +321,10 @@ public_key(#'OTPSubjectPublicKeyInfo'{algorithm = #'PublicKeyAlgorithm'{algorith
public_key(#'OTPSubjectPublicKeyInfo'{algorithm = #'PublicKeyAlgorithm'{algorithm = ?'rsaEncryption'},
subjectPublicKey = Key}) ->
Key;
+public_key(#'OTPSubjectPublicKeyInfo'{algorithm = #'PublicKeyAlgorithm'{algorithm = ?'id-RSASSA-PSS',
+ parameters = Params},
+ subjectPublicKey = Key}) ->
+ {Key, Params};
public_key(#'OTPSubjectPublicKeyInfo'{algorithm = #'PublicKeyAlgorithm'{algorithm = ?'id-dsa',
parameters = {params, Params}},
subjectPublicKey = Key}) ->
diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl
index e9eb78203c..f08a7e6b00 100644
--- a/lib/ssl/src/ssl_cipher.erl
+++ b/lib/ssl/src/ssl_cipher.erl
@@ -34,18 +34,43 @@
-include("tls_handshake_1_3.hrl").
-include_lib("public_key/include/public_key.hrl").
--export([security_parameters/2, security_parameters/3, security_parameters_1_3/2,
- cipher_init/3, nonce_seed/2, decipher/6, cipher/5, aead_encrypt/6, aead_decrypt/6,
- suites/1, all_suites/1, crypto_support_filters/0,
- chacha_suites/1, anonymous_suites/1, psk_suites/1, psk_suites_anon/1,
- srp_suites/1, srp_suites_anon/1,
- rc4_suites/1, des_suites/1, rsa_suites/1,
- filter/3, filter_suites/1, filter_suites/2,
- hash_algorithm/1, sign_algorithm/1, is_acceptable_hash/2, is_fallback/1,
- random_bytes/1, calc_mac_hash/4, calc_mac_hash/6,
- is_stream_ciphersuite/1, signature_scheme/1,
- scheme_to_components/1, hash_size/1, effective_key_bits/1,
- key_material/1, signature_algorithm_to_scheme/1]).
+-export([security_parameters/2,
+ security_parameters/3,
+ security_parameters_1_3/2,
+ cipher_init/3,
+ nonce_seed/2,
+ decipher/6,
+ cipher/5,
+ aead_encrypt/6,
+ aead_decrypt/6,
+ suites/1,
+ all_suites/1,
+ crypto_support_filters/0,
+ anonymous_suites/1,
+ psk_suites/1,
+ psk_suites_anon/1,
+ srp_suites/1,
+ srp_suites_anon/1,
+ rc4_suites/1,
+ des_suites/1,
+ rsa_suites/1,
+ filter/3,
+ filter_suites/1,
+ filter_suites/2,
+ hash_algorithm/1,
+ sign_algorithm/1,
+ is_acceptable_hash/2,
+ is_fallback/1,
+ random_bytes/1,
+ calc_mac_hash/4,
+ calc_mac_hash/6,
+ is_stream_ciphersuite/1,
+ signature_scheme/1,
+ scheme_to_components/1,
+ hash_size/1,
+ effective_key_bits/1,
+ key_material/1,
+ signature_algorithm_to_scheme/1]).
%% RFC 8446 TLS 1.3
-export([generate_client_shares/1,
@@ -303,7 +328,6 @@ suites({_, Minor}) ->
all_suites({3, _} = Version) ->
suites(Version)
- ++ chacha_suites(Version)
++ psk_suites(Version)
++ srp_suites(Version)
++ rsa_suites(Version)
@@ -312,20 +336,6 @@ all_suites({3, _} = Version) ->
all_suites(Version) ->
dtls_v1:all_suites(Version).
-%%--------------------------------------------------------------------
--spec chacha_suites(ssl_record:ssl_version() | integer()) ->
- [ssl_cipher_format:cipher_suite()].
-%%
-%% Description: Returns list of the chacha cipher suites, only supported
-%% if explicitly set by user for now due to interop problems, proably need
-%% to be fixed in crypto.
-%%--------------------------------------------------------------------
-chacha_suites({3, _}) ->
- [?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
- ?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
- ?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256];
-chacha_suites(_) ->
- [].
%%--------------------------------------------------------------------
-spec anonymous_suites(ssl_record:ssl_version() | integer()) ->
@@ -982,8 +992,20 @@ scheme_to_components(ecdsa_sha1) -> {sha1, ecdsa, undefined};
%% Handling legacy signature algorithms
scheme_to_components({Hash,Sign}) -> {Hash, Sign, undefined}.
-
-%% TODO: Add support for ed25519, ed448, rsa_pss*
+signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'id-RSASSA-PSS',
+ parameters = #'RSASSA-PSS-params'{
+ maskGenAlgorithm =
+ #'MaskGenAlgorithm'{algorithm = ?'id-mgf1',
+ parameters = HashAlgo}}}) ->
+ #'HashAlgorithm'{algorithm = HashOid} = HashAlgo,
+ case public_key:pkix_hash_type(HashOid) of
+ sha256 ->
+ rsa_pss_pss_sha256;
+ sha384 ->
+ rsa_pss_pss_sha384;
+ sha512 ->
+ rsa_pss_pss_sha512
+ end;
signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?sha256WithRSAEncryption}) ->
rsa_pkcs1_sha256;
signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?sha384WithRSAEncryption}) ->
@@ -1001,8 +1023,18 @@ signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'sha-1WithRSAEn
signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?sha1WithRSAEncryption}) ->
rsa_pkcs1_sha1;
signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'ecdsa-with-SHA1'}) ->
- ecdsa_sha1.
-
+ ecdsa_sha1;
+signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'id-Ed25519'}) ->
+ eddsa_ed25519;
+signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'id-Ed448'}) ->
+ eddsa_ed448;
+signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'rsaEncryption',
+ parameters = ?NULL}) ->
+ rsa_pkcs1_sha1;
+signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'rsaEncryption'}) ->
+ rsa_pss_rsae;
+signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'id-RSASSA-PSS'}) ->
+ rsa_pss_pss.
%% RFC 5246: 6.2.3.2. CBC Block Cipher
%%
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
index 8e2e794280..8ee9261c38 100644
--- a/lib/ssl/src/ssl_connection.erl
+++ b/lib/ssl/src/ssl_connection.erl
@@ -1652,7 +1652,10 @@ 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, ecc = ECCCurve},
+ 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),
@@ -1665,12 +1668,19 @@ connection_info(#state{static_env = #static_env{protocol_cb = Connection},
_ ->
[]
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},
- {cipher_suite, ssl_cipher_format:suite_legacy(CipherSuiteDef)},
{selected_cipher_suite, CipherSuiteDef},
- {sni_hostname, SNIHostname} | CurveInfo] ++ ssl_options_list(Opts).
+ {sni_hostname, SNIHostname},
+ {srp_username, SrpUsername} | CurveInfo] ++ MFLInfo ++ ssl_options_list(Opts).
security_info(#state{connection_states = ConnectionStates}) ->
#{security_parameters :=
@@ -2807,6 +2817,9 @@ 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(
diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl
index 89bbdd0f54..b7e9e769ea 100644
--- a/lib/ssl/src/ssl_connection.hrl
+++ b/lib/ssl/src/ssl_connection.hrl
@@ -67,6 +67,7 @@
%% Ext handling
hello, %%:: #client_hello{} | #server_hello{}
sni_hostname = undefined,
+ max_frag_enum :: undefined | {max_frag_enum, integer()},
expecting_next_protocol_negotiation = false ::boolean(),
next_protocol = undefined :: undefined | binary(),
alpn = undefined, %% Used in TLS 1.3
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index e1d629a3e3..c17062d888 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -48,7 +48,7 @@
%% Create handshake messages
-export([hello_request/0, server_hello/4, server_hello_done/0,
certificate/4, client_certificate_verify/6, certificate_request/5, key_exchange/3,
- finished/5, next_protocol/1]).
+ finished/5, next_protocol/1, digitally_signed/5]).
%% Handle handshake messages
-export([certify/7, certificate_verify/6, verify_signature/5,
@@ -73,11 +73,11 @@
%% Extensions handling
-export([client_hello_extensions/7,
- handle_client_hello_extensions/9, %% Returns server hello extensions
- handle_server_hello_extensions/9, select_curve/2, select_curve/3,
+ handle_client_hello_extensions/10, %% Returns server hello extensions
+ handle_server_hello_extensions/10, select_curve/2, select_curve/3,
select_hashsign/4, select_hashsign/5,
select_hashsign_algs/3, empty_extensions/2, add_server_share/3,
- add_alpn/2, add_selected_version/1, decode_alpn/1
+ add_alpn/2, add_selected_version/1, decode_alpn/1, max_frag_enum/1
]).
-export([get_cert_params/1,
@@ -171,7 +171,7 @@ client_certificate_verify(OwnCert, MasterSecret, Version,
false ->
Hashes =
calc_certificate_verify(Version, HashAlgo, MasterSecret, Handshake),
- Signed = digitally_signed(Version, Hashes, HashAlgo, PrivateKey),
+ Signed = digitally_signed(Version, Hashes, HashAlgo, PrivateKey, SignAlgo),
#certificate_verify{signature = Signed, hashsign_algorithm = {HashAlgo, SignAlgo}}
end.
@@ -400,27 +400,34 @@ certificate_verify(Signature, PublicKeyInfo, Version,
%%
%% Description: Checks that a public_key signature is valid.
%%--------------------------------------------------------------------
-verify_signature(_Version, _Hash, {_HashAlgo, anon}, _Signature, _) ->
- true;
-verify_signature({3, Minor}, Hash, {HashAlgo, rsa_pss_rsae}, Signature, {?rsaEncryption, PubKey, _PubKeyParams})
- when Minor >= 3 ->
- public_key:verify({digest, Hash}, HashAlgo, Signature, PubKey,
- [{rsa_padding, rsa_pkcs1_pss_padding},
- {rsa_pss_saltlen, -1},
- {rsa_mgf1_md, HashAlgo}]);
-verify_signature({3, Minor}, Hash, {HashAlgo, rsa}, Signature, {?rsaEncryption, PubKey, _PubKeyParams})
+verify_signature({3, 4}, Hash, {HashAlgo, SignAlgo}, Signature,
+ {_, PubKey, PubKeyParams}) when SignAlgo == rsa_pss_rsae;
+ SignAlgo == rsa_pss_pss ->
+ Options = verify_options(SignAlgo, HashAlgo, PubKeyParams),
+ public_key:verify(Hash, HashAlgo, Signature, PubKey, Options);
+verify_signature({3, 3}, Hash, {HashAlgo, SignAlgo}, Signature,
+ {_, PubKey, PubKeyParams}) when SignAlgo == rsa_pss_rsae;
+ SignAlgo == rsa_pss_pss ->
+ Options = verify_options(SignAlgo, HashAlgo, PubKeyParams),
+ public_key:verify({digest, Hash}, HashAlgo, Signature, PubKey, Options);
+verify_signature({3, Minor}, Hash, {HashAlgo, SignAlgo}, Signature, {?rsaEncryption, PubKey, PubKeyParams})
when Minor >= 3 ->
- public_key:verify({digest, Hash}, HashAlgo, Signature, PubKey);
-verify_signature(_Version, Hash, _HashAlgo, Signature, {?rsaEncryption, PubKey, _PubKeyParams}) ->
+ Options = verify_options(SignAlgo, HashAlgo, PubKeyParams),
+ public_key:verify({digest, Hash}, HashAlgo, Signature, PubKey, Options);
+verify_signature({3, Minor}, Hash, _HashAlgo, Signature, {?rsaEncryption, PubKey, _PubKeyParams}) when Minor =< 2 ->
case public_key:decrypt_public(Signature, PubKey,
[{rsa_pad, rsa_pkcs1_padding}]) of
Hash -> true;
- _ -> false
+ _ -> false
end;
-verify_signature(_Version, Hash, {HashAlgo, dsa}, Signature, {?'id-dsa', PublicKey, PublicKeyParams}) ->
- public_key:verify({digest, Hash}, HashAlgo, Signature, {PublicKey, PublicKeyParams});
+verify_signature({3, 4}, Hash, {HashAlgo, _SignAlgo}, Signature, {?'id-ecPublicKey', PubKey, PubKeyParams}) ->
+ public_key:verify(Hash, HashAlgo, Signature, {PubKey, PubKeyParams});
verify_signature(_, Hash, {HashAlgo, _SignAlg}, Signature,
{?'id-ecPublicKey', PublicKey, PublicKeyParams}) ->
+ public_key:verify({digest, Hash}, HashAlgo, Signature, {PublicKey, PublicKeyParams});
+verify_signature({3, Minor}, _Hash, {_HashAlgo, anon}, _Signature, _) when Minor =< 3 ->
+ true;
+verify_signature({3, Minor}, Hash, {HashAlgo, dsa}, Signature, {?'id-dsa', PublicKey, PublicKeyParams}) when Minor =< 3->
public_key:verify({digest, Hash}, HashAlgo, Signature, {PublicKey, PublicKeyParams}).
%%--------------------------------------------------------------------
@@ -695,6 +702,10 @@ encode_extensions([#sni{hostname = Hostname} | Rest], Acc) ->
?BYTE(?SNI_NAMETYPE_HOST_NAME),
?UINT16(HostLen), HostnameBin/binary,
Acc/binary>>);
+encode_extensions([#max_frag_enum{enum = MaxFragEnum} | Rest], Acc) ->
+ ExtLength = 1,
+ encode_extensions(Rest, <<?UINT16(?MAX_FRAGMENT_LENGTH_EXT), ?UINT16(ExtLength), ?BYTE(MaxFragEnum),
+ Acc/binary>>);
encode_extensions([#client_hello_versions{versions = Versions0} | Rest], Acc) ->
Versions = encode_versions(Versions0),
VerLen = byte_size(Versions),
@@ -1088,7 +1099,8 @@ client_hello_extensions(Version, CipherSuites, SslOpts, ConnectionStates, Renego
add_tls12_extensions(_Version,
#{alpn_advertised_protocols := AlpnAdvertisedProtocols,
next_protocol_selector := NextProtocolSelector,
- server_name_indication := ServerNameIndication} = SslOpts,
+ server_name_indication := ServerNameIndication,
+ max_fragment_length := MaxFragmentLength} = SslOpts,
ConnectionStates,
Renegotiation) ->
SRP = srp_user(SslOpts),
@@ -1099,7 +1111,8 @@ add_tls12_extensions(_Version,
next_protocol_negotiation =>
encode_client_protocol_negotiation(NextProtocolSelector,
Renegotiation),
- sni => sni(ServerNameIndication)
+ sni => sni(ServerNameIndication),
+ max_frag_enum => max_frag_enum(MaxFragmentLength)
}.
@@ -1284,18 +1297,27 @@ handle_client_hello_extensions(RecordCB, Random, ClientCipherSuites,
alpn_preferred_protocols := ALPNPreferredProtocols} = Opts,
#session{cipher_suite = NegotiatedCipherSuite,
compression_method = Compression} = Session0,
- ConnectionStates0, Renegotiation) ->
+ ConnectionStates0, Renegotiation, IsResumed) ->
Session = handle_srp_extension(maps:get(srp, Exts, undefined), Session0),
+ MaxFragEnum = handle_mfl_extension(maps:get(max_frag_enum, Exts, undefined)),
+ ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0),
ConnectionStates = handle_renegotiation_extension(server, RecordCB, Version, maps:get(renegotiation_info, Exts, undefined),
Random, NegotiatedCipherSuite,
ClientCipherSuites, Compression,
- ConnectionStates0, Renegotiation, SecureRenegotation),
+ ConnectionStates1, Renegotiation, SecureRenegotation),
Empty = empty_extensions(Version, server_hello),
+ %% RFC 6066 - server doesn't include max_fragment_length for resumed sessions
+ ServerMaxFragEnum = if IsResumed ->
+ undefined;
+ true ->
+ MaxFragEnum
+ end,
ServerHelloExtensions = Empty#{renegotiation_info => renegotiation_info(RecordCB, server,
ConnectionStates, Renegotiation),
ec_point_formats => server_ecc_extension(Version,
- maps:get(ec_point_formats, Exts, undefined))
+ maps:get(ec_point_formats, Exts, undefined)),
+ max_frag_enum => ServerMaxFragEnum
},
%% If we receive an ALPN extension and have ALPN configured for this connection,
@@ -1318,13 +1340,28 @@ handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression,
Exts, Version,
#{secure_renegotiate := SecureRenegotation,
next_protocol_selector := NextProtoSelector},
- ConnectionStates0, Renegotiation) ->
+ ConnectionStates0, Renegotiation, IsNew) ->
ConnectionStates = handle_renegotiation_extension(client, RecordCB, Version,
maps:get(renegotiation_info, Exts, undefined), Random,
CipherSuite, undefined,
Compression, ConnectionStates0,
Renegotiation, SecureRenegotation),
+ %% RFC 6066: handle received/expected maximum fragment length
+ if IsNew ->
+ ServerMaxFragEnum = maps:get(max_frag_enum, Exts, undefined),
+ #{current_write := #{max_fragment_length := ConnMaxFragLen}} = ConnectionStates,
+ ClientMaxFragEnum = max_frag_enum(ConnMaxFragLen),
+
+ if ServerMaxFragEnum == ClientMaxFragEnum ->
+ ok;
+ true ->
+ throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER))
+ end;
+ true ->
+ ok
+ end,
+
%% If we receive an ALPN extension then this is the protocol selected,
%% otherwise handle the NPN extension.
ALPN = maps:get(alpn, Exts, undefined),
@@ -1391,7 +1428,7 @@ select_hashsign({#hash_sign_algos{hash_sign_algos = ClientHashSigns},
Cert, KeyExAlgo, SupportedHashSigns, {Major, Minor})
when Major >= 3 andalso Minor >= 3 ->
ClientSignatureSchemes = get_signature_scheme(ClientSignatureSchemes0),
- {SignAlgo0, Param, PublicKeyAlgo0} = get_cert_params(Cert),
+ {SignAlgo0, Param, PublicKeyAlgo0, _} = get_cert_params(Cert),
SignAlgo = sign_algo(SignAlgo0),
PublicKeyAlgo = public_key_algo(PublicKeyAlgo0),
@@ -1445,7 +1482,7 @@ select_hashsign(#certificate_request{
Cert,
SupportedHashSigns,
{Major, Minor}) when Major >= 3 andalso Minor >= 3->
- {SignAlgo0, Param, PublicKeyAlgo0} = get_cert_params(Cert),
+ {SignAlgo0, Param, PublicKeyAlgo0, _} = get_cert_params(Cert),
SignAlgo = sign_algo(SignAlgo0),
PublicKeyAlgo = public_key_algo(PublicKeyAlgo0),
@@ -1468,7 +1505,7 @@ select_hashsign(#certificate_request{
?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm)
end;
select_hashsign(#certificate_request{certificate_types = Types}, Cert, _, Version) ->
- {_, _, PublicKeyAlgo0} = get_cert_params(Cert),
+ {_, _, PublicKeyAlgo0, _} = get_cert_params(Cert),
PublicKeyAlgo = public_key_algo(PublicKeyAlgo0),
%% Check cert even for TLS 1.0/1.1
@@ -1484,14 +1521,23 @@ select_hashsign(#certificate_request{certificate_types = Types}, Cert, _, Versio
%% - signature algorithm
%% - parameters of the signature algorithm
%% - public key algorithm (key type)
+%% - RSA key size in bytes
get_cert_params(Cert) ->
#'OTPCertificate'{tbsCertificate = TBSCert,
signatureAlgorithm =
{_,SignAlgo, Param}} = public_key:pkix_decode_cert(Cert, otp),
- #'OTPSubjectPublicKeyInfo'{algorithm = {_, PublicKeyAlgo, _}} =
+ #'OTPSubjectPublicKeyInfo'{algorithm = {_, PublicKeyAlgo, _},
+ subjectPublicKey = PublicKey} =
TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo,
- {SignAlgo, Param, PublicKeyAlgo}.
-
+ RSAKeySize =
+ case PublicKey of
+ #'RSAPublicKey'{modulus = Modulus} ->
+ %% Get RSA key size in bytes
+ byte_size(binary:encode_unsigned(Modulus));
+ _ ->
+ undefined
+ end,
+ {SignAlgo, Param, PublicKeyAlgo, RSAKeySize}.
get_signature_scheme(undefined) ->
undefined;
@@ -1553,6 +1599,8 @@ extension_value(#hash_sign_algos{hash_sign_algos = Algos}) ->
Algos;
extension_value(#alpn{extension_data = Data}) ->
Data;
+extension_value(#max_frag_enum{enum = Enum}) ->
+ Enum;
extension_value(#next_protocol_negotiation{extension_data = Data}) ->
Data;
extension_value(#srp{username = Name}) ->
@@ -1680,9 +1728,10 @@ validation_fun_and_state(undefined, Role, CertDbHandle, CertDbRef,
SslState)
end, {Role, CertDbHandle, CertDbRef, {ServerNameIndication, CustomizeHostCheck}, CRLCheck, CRLDbHandle}}.
-apply_user_fun(Fun, OtpCert, VerifyResult, UserState0,
+apply_user_fun(Fun, OtpCert, VerifyResult0, UserState0,
{_, CertDbHandle, CertDbRef, _, CRLCheck, CRLDbHandle} = SslState, CertPath, LogLevel) when
- (VerifyResult == valid) or (VerifyResult == valid_peer) ->
+ (VerifyResult0 == valid) or (VerifyResult0 == valid_peer) ->
+ VerifyResult = maybe_check_hostname(OtpCert, VerifyResult0, SslState),
case Fun(OtpCert, VerifyResult, UserState0) of
{Valid, UserState} when (Valid == valid) or (Valid == valid_peer) ->
case crl_check(OtpCert, CRLCheck, CertDbHandle, CertDbRef,
@@ -1705,6 +1754,16 @@ apply_user_fun(Fun, OtpCert, ExtensionOrError, UserState0, SslState, _CertPath,
{unknown, {SslState, UserState}}
end.
+maybe_check_hostname(OtpCert, valid_peer, SslState) ->
+ case ssl_certificate:validate(OtpCert, valid_peer, SslState) of
+ {valid, _} ->
+ valid_peer;
+ {fail, Reason} ->
+ Reason
+ end;
+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);
@@ -1780,30 +1839,64 @@ path_validation_alert({bad_cert, unknown_ca}) ->
path_validation_alert(Reason) ->
?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason).
-digitally_signed(Version, Hashes, HashAlgo, PrivateKey) ->
- try do_digitally_signed(Version, Hashes, HashAlgo, PrivateKey) of
+digitally_signed(Version, Hashes, HashAlgo, PrivateKey, SignAlgo) ->
+ try do_digitally_signed(Version, Hashes, HashAlgo, PrivateKey, SignAlgo) of
Signature ->
Signature
catch
error:badkey->
throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, bad_key(PrivateKey)))
end.
-do_digitally_signed({3, Minor}, Hash, HashAlgo, #{algorithm := Alg} = Engine)
- when Minor >= 3 ->
- crypto:sign(Alg, HashAlgo, {digest, Hash}, maps:remove(algorithm, Engine));
-do_digitally_signed({3, Minor}, Hash, HashAlgo, Key) when Minor >= 3 ->
- public_key:sign({digest, Hash}, HashAlgo, Key);
-do_digitally_signed(_Version, Hash, _HashAlgo, #'RSAPrivateKey'{} = Key) ->
- public_key:encrypt_private(Hash, Key,
- [{rsa_pad, rsa_pkcs1_padding}]);
-do_digitally_signed({3, _}, Hash, _,
- #{algorithm := rsa} = Engine) ->
+
+do_digitally_signed({3, Minor}, Hash, _,
+ #{algorithm := rsa} = Engine, rsa) when Minor =< 2->
crypto:private_encrypt(rsa, Hash, maps:remove(algorithm, Engine),
rsa_pkcs1_padding);
-do_digitally_signed({3, _}, Hash, HashAlgo, #{algorithm := Alg} = Engine) ->
- crypto:sign(Alg, HashAlgo, {digest, Hash}, maps:remove(algorithm, Engine));
-do_digitally_signed(_Version, Hash, HashAlgo, Key) ->
+do_digitally_signed({3, Minor}, Hash, HashAlgo, #{algorithm := Alg} = Engine, SignAlgo)
+ when Minor > 3 ->
+ Options = signature_options(SignAlgo, HashAlgo),
+ crypto:sign(Alg, HashAlgo, Hash, maps:remove(algorithm, Engine), Options);
+do_digitally_signed({3, Minor}, Hash, HashAlgo, #{algorithm := Alg} = Engine, SignAlgo)
+ when Minor > 3 ->
+ Options = signature_options(SignAlgo, HashAlgo),
+ crypto:sign(Alg, HashAlgo, Hash, maps:remove(algorithm, Engine), Options);
+do_digitally_signed({3, 3}, Hash, HashAlgo, #{algorithm := Alg} = Engine, SignAlgo) ->
+ Options = signature_options(SignAlgo, HashAlgo),
+ crypto:sign(Alg, HashAlgo, {digest, Hash}, maps:remove(algorithm, Engine), Options);
+do_digitally_signed({3, 4}, Hash, HashAlgo, {#'RSAPrivateKey'{} = Key,
+ #'RSASSA-PSS-params'{}}, SignAlgo) ->
+ Options = signature_options(SignAlgo, HashAlgo),
+ public_key:sign(Hash, HashAlgo, Key, Options);
+do_digitally_signed({3, 4}, Hash, HashAlgo, Key, SignAlgo) ->
+ Options = signature_options(SignAlgo, HashAlgo),
+ public_key:sign(Hash, HashAlgo, Key, Options);
+do_digitally_signed({3, Minor}, Hash, HashAlgo, Key, SignAlgo) when Minor >= 3 ->
+ Options = signature_options(HashAlgo, SignAlgo),
+ public_key:sign({digest,Hash}, HashAlgo, Key, Options);
+do_digitally_signed({3, Minor}, Hash, _HashAlgo, #'RSAPrivateKey'{} = Key, rsa) when Minor =< 2 ->
+ public_key:encrypt_private(Hash, Key,
+ [{rsa_pad, rsa_pkcs1_padding}]);
+do_digitally_signed(_Version, Hash, HashAlgo, Key, _SignAlgo) ->
public_key:sign({digest, Hash}, HashAlgo, Key).
+
+signature_options(SignAlgo, HashAlgo) when SignAlgo =:= rsa_pss_rsae orelse
+ SignAlgo =:= rsa_pss_pss ->
+ pss_options(HashAlgo);
+signature_options(_, _) ->
+ [].
+
+verify_options(SignAlgo, HashAlgo, _KeyParams)
+ when SignAlgo =:= rsa_pss_rsae orelse
+ SignAlgo =:= rsa_pss_pss ->
+ pss_options(HashAlgo);
+verify_options(_, _, _) ->
+ [].
+
+pss_options(HashAlgo) ->
+ %% of the digest algorithm: rsa_pss_saltlen = -1
+ [{rsa_padding, rsa_pkcs1_pss_padding},
+ {rsa_pss_saltlen, -1},
+ {rsa_mgf1_md, HashAlgo}].
bad_key(#'DSAPrivateKey'{}) ->
unacceptable_dsa_key;
@@ -2160,7 +2253,7 @@ enc_server_key_exchange(Version, Params, {HashAlgo, SignAlgo},
server_key_exchange_hash(HashAlgo, <<ClientRandom/binary,
ServerRandom/binary,
EncParams/binary>>),
- Signature = digitally_signed(Version, Hash, HashAlgo, PrivateKey),
+ Signature = digitally_signed(Version, Hash, HashAlgo, PrivateKey, SignAlgo),
#server_key_params{params = Params,
params_bin = EncParams,
hashsign = {HashAlgo, SignAlgo},
@@ -2574,6 +2667,10 @@ decode_extensions(<<?UINT16(?SNI_EXT), ?UINT16(Len),
decode_extensions(Rest, Version, MessageType,
Acc#{sni => dec_sni(NameList)});
+decode_extensions(<<?UINT16(?MAX_FRAGMENT_LENGTH_EXT), ?UINT16(1), ?BYTE(MaxFragEnum), Rest/binary>>,
+ Version, MessageType, Acc) ->
+ %% RFC 6066 Section 4
+ decode_extensions(Rest, Version, MessageType, Acc#{max_frag_enum => #max_frag_enum{enum = MaxFragEnum}});
decode_extensions(<<?UINT16(?SUPPORTED_VERSIONS_EXT), ?UINT16(Len),
ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) when Len > 2 ->
<<?BYTE(_),Versions/binary>> = ExtData,
@@ -2935,6 +3032,13 @@ handle_alpn_extension([ServerProtocol|Tail], ClientProtocols) ->
false -> handle_alpn_extension(Tail, ClientProtocols)
end.
+handle_mfl_extension(#max_frag_enum{enum = Enum}=MaxFragEnum) when Enum >= 1, Enum =< 4 ->
+ MaxFragEnum;
+handle_mfl_extension(#max_frag_enum{}) ->
+ throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER));
+handle_mfl_extension(_) ->
+ undefined.
+
handle_next_protocol(undefined,
_NextProtocolSelector, _Renegotiating) ->
undefined;
@@ -3153,6 +3257,18 @@ sni(disable) ->
sni(Hostname) ->
#sni{hostname = Hostname}.
+%% convert max_fragment_length (in bytes) to the RFC 6066 ENUM
+max_frag_enum(?MAX_FRAGMENT_LENGTH_BYTES_1) ->
+ #max_frag_enum{enum = 1};
+max_frag_enum(?MAX_FRAGMENT_LENGTH_BYTES_2) ->
+ #max_frag_enum{enum = 2};
+max_frag_enum(?MAX_FRAGMENT_LENGTH_BYTES_3) ->
+ #max_frag_enum{enum = 3};
+max_frag_enum(?MAX_FRAGMENT_LENGTH_BYTES_4) ->
+ #max_frag_enum{enum = 4};
+max_frag_enum(undefined) ->
+ undefined.
+
renegotiation_info(_, client, _, false) ->
#renegotiation_info{renegotiated_connection = undefined};
renegotiation_info(_RecordCB, server, ConnectionStates, false) ->
@@ -3270,7 +3386,7 @@ empty_extensions() ->
empty_extensions({3,4}, client_hello) ->
#{
sni => undefined,
- %% max_fragment_length => undefined,
+ %% max_frag_enum => undefined,
%% status_request => undefined,
elliptic_curves => undefined,
signature_algs => undefined,
diff --git a/lib/ssl/src/ssl_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl
index a772567846..ac397a8d88 100644
--- a/lib/ssl/src/ssl_handshake.hrl
+++ b/lib/ssl/src/ssl_handshake.hrl
@@ -389,8 +389,18 @@
hostname = undefined
}).
+%% enum{ 2^9(1), 2^10(2), 2^11(3), 2^12(4), (255) } MaxFragmentLength;
+-define(MAX_FRAGMENT_LENGTH_EXT, 1).
+-define(MAX_FRAGMENT_LENGTH_BYTES_1, 512).
+-define(MAX_FRAGMENT_LENGTH_BYTES_2, 1024).
+-define(MAX_FRAGMENT_LENGTH_BYTES_3, 2048).
+-define(MAX_FRAGMENT_LENGTH_BYTES_4, 4096).
+
+-record(max_frag_enum, {
+ enum = undefined %% contains the enum value 1..4
+ }).
+
%% Other possible values from RFC 6066, not supported
--define(MAX_FRAGMENT_LENGTH, 1).
-define(CLIENT_CERTIFICATE_URL, 2).
-define(TRUSTED_CA_KEYS, 3).
-define(TRUNCATED_HMAC, 4).
diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl
index cb41742404..2da39b199f 100644
--- a/lib/ssl/src/ssl_internal.hrl
+++ b/lib/ssl/src/ssl_internal.hrl
@@ -25,7 +25,6 @@
-include_lib("public_key/include/public_key.hrl").
--define(VSN, "8.2.6").
-define(SECRET_PRINTOUT, "***").
-type reason() :: any().
@@ -155,6 +154,7 @@
log_level => {notice, [versions]},
max_handshake_size => {?DEFAULT_MAX_HANDSHAKE_SIZE, [versions]},
middlebox_comp_mode => {true, [versions]},
+ max_fragment_length => {undefined, [versions]},
next_protocol_selector => {undefined, [versions]},
next_protocols_advertised => {undefined, [versions]},
padding_check => {true, [versions]},
@@ -177,12 +177,9 @@
supported_groups => {undefined, [versions]},
use_ticket => {undefined, [versions]},
user_lookup_fun => {undefined, [versions]},
- validate_extensions_fun => {undefined, [versions]},
verify => {verify_none, [versions,
fail_if_no_peer_cert,
- partial_chain,
- verify_client_once]},
- verify_client_once => {false, [versions]},
+ partial_chain]},
verify_fun =>
{
{fun(_,{bad_cert, _}, UserState) ->
diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl
index 867d2cfc5a..c19c6eeea9 100644
--- a/lib/ssl/src/ssl_record.erl
+++ b/lib/ssl/src/ssl_record.erl
@@ -40,6 +40,7 @@
set_renegotiation_flag/2,
set_client_verify_data/3,
set_server_verify_data/3,
+ set_max_fragment_length/2,
empty_connection_state/2, initial_connection_state/2, record_protocol_role/1,
step_encryption_state/1]).
@@ -203,6 +204,33 @@ set_renegotiation_flag(Flag, #{current_read := CurrentRead0,
pending_write => PendingWrite}.
%%--------------------------------------------------------------------
+-spec set_max_fragment_length(term(), connection_states()) -> connection_states().
+%%
+%% Description: Set maximum fragment length in all connection states
+%%--------------------------------------------------------------------
+set_max_fragment_length(#max_frag_enum{enum = MaxFragEnum},
+ #{current_read := CurrentRead0,
+ current_write := CurrentWrite0,
+ pending_read := PendingRead0,
+ pending_write := PendingWrite0}
+ = ConnectionStates) ->
+ MaxFragmentLength = if MaxFragEnum == 1 -> ?MAX_FRAGMENT_LENGTH_BYTES_1;
+ MaxFragEnum == 2 -> ?MAX_FRAGMENT_LENGTH_BYTES_2;
+ MaxFragEnum == 3 -> ?MAX_FRAGMENT_LENGTH_BYTES_3;
+ MaxFragEnum == 4 -> ?MAX_FRAGMENT_LENGTH_BYTES_4
+ end,
+ CurrentRead = CurrentRead0#{max_fragment_length => MaxFragmentLength},
+ CurrentWrite = CurrentWrite0#{max_fragment_length => MaxFragmentLength},
+ PendingRead = PendingRead0#{max_fragment_length => MaxFragmentLength},
+ PendingWrite = PendingWrite0#{max_fragment_length => MaxFragmentLength},
+ ConnectionStates#{current_read => CurrentRead,
+ current_write => CurrentWrite,
+ pending_read => PendingRead,
+ pending_write => PendingWrite};
+set_max_fragment_length(_,ConnectionStates) ->
+ ConnectionStates.
+
+%%--------------------------------------------------------------------
-spec set_client_verify_data(current_read | current_write | current_both,
binary(), connection_states())->
connection_states().
@@ -424,7 +452,8 @@ empty_connection_state(ConnectionEnd, BeastMitigation) ->
mac_secret => undefined,
secure_renegotiation => undefined,
client_verify_data => undefined,
- server_verify_data => undefined
+ server_verify_data => undefined,
+ max_fragment_length => undefined
}.
empty_security_params(ConnectionEnd = ?CLIENT) ->
@@ -461,7 +490,8 @@ initial_connection_state(ConnectionEnd, BeastMitigation) ->
mac_secret => undefined,
secure_renegotiation => undefined,
client_verify_data => undefined,
- server_verify_data => undefined
+ server_verify_data => undefined,
+ max_fragment_length => undefined
}.
initial_security_params(ConnectionEnd) ->
diff --git a/lib/ssl/src/ssl_record.hrl b/lib/ssl/src/ssl_record.hrl
index c0555046c3..9aa598daed 100644
--- a/lib/ssl/src/ssl_record.hrl
+++ b/lib/ssl/src/ssl_record.hrl
@@ -154,6 +154,8 @@
-define(MAX_COMPRESSED_LENGTH, (?MAX_PLAIN_TEXT_LENGTH+1024)).
-define(MAX_CIPHER_TEXT_LENGTH, (?MAX_PLAIN_TEXT_LENGTH+2048)).
-define(TLS13_MAX_CIPHER_TEXT_LENGTH, (?MAX_PLAIN_TEXT_LENGTH+256)).
+-define(MAX_PADDING_LENGTH,256).
+-define(MAX_MAC_LENGTH,32).
%% -record(protocol_version, {
%% major, % unit 8
diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl
index 4a02d34a6b..5a41b69e54 100644
--- a/lib/ssl/src/tls_connection.erl
+++ b/lib/ssl/src/tls_connection.erl
@@ -604,8 +604,11 @@ init({call, From}, {start, Timeout},
%% 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, ConnectionStates0, Handshake0),
+ encode_handshake(Hello1, HelloVersion, ConnectionStates1, Handshake0),
tls_socket:send(Transport, Socket, BinMsg),
ssl_logger:debug(LogLevel, outbound, 'handshake', Hello1),
@@ -718,8 +721,9 @@ hello(internal, #server_hello{} = Hello,
connection_env = #connection_env{negotiated_version = ReqVersion} = CEnv,
static_env = #static_env{role = client},
handshake_env = #handshake_env{renegotiation = {Renegotiation, _}},
+ session = #session{session_id = OldId},
ssl_options = SslOptions} = State) ->
- case tls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of
+ case tls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation, OldId) of
#alert{} = Alert -> %%TODO
ssl_connection:handle_own_alert(Alert, ReqVersion, hello,
State#state{connection_env =
@@ -1164,7 +1168,8 @@ next_tls_record(Data, StateName,
_ ->
State0#state.connection_env#connection_env.negotiated_version
end,
- case tls_record:get_tls_records(Data, Versions, Buf0, SslOpts) of
+ #{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 =
@@ -1196,10 +1201,18 @@ handle_info({Protocol, _, Data}, StateName,
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
- } = State) ->
- next_event(StateName, no_record,
- State#state{protocol_specific = PS#{active_n_toggle => true}});
+ } = 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,
diff --git a/lib/ssl/src/tls_connection_1_3.erl b/lib/ssl/src/tls_connection_1_3.erl
index b440d65706..0b5ff98474 100644
--- a/lib/ssl/src/tls_connection_1_3.erl
+++ b/lib/ssl/src/tls_connection_1_3.erl
@@ -218,6 +218,8 @@ 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) ->
case tls_handshake_1_3:do_wait_ee(EE, State0) of
+ #alert{} = Alert ->
+ ssl_connection:handle_own_alert(Alert, {3,4}, wait_ee, State0);
{State1, NextState} ->
tls_connection:next_event(NextState, no_record, State1)
end;
diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl
index f279e041be..897133846f 100644
--- a/lib/ssl/src/tls_handshake.erl
+++ b/lib/ssl/src/tls_handshake.erl
@@ -36,7 +36,7 @@
-include_lib("kernel/include/logger.hrl").
%% Handshake handling
--export([client_hello/9, hello/4]).
+-export([client_hello/9, hello/5, hello/4]).
%% Handshake encoding
-export([encode_handshake/2]).
@@ -95,11 +95,11 @@ client_hello(_Host, _Port, ConnectionStates,
}.
%%--------------------------------------------------------------------
--spec hello(#server_hello{} | #client_hello{}, ssl_options(),
+-spec hello(#server_hello{}, ssl_options(),
ssl_record:connection_states() | {inet:port_number(), #session{}, db_handle(),
atom(), ssl_record:connection_states(),
binary() | undefined, ssl:kex_algo()},
- boolean()) ->
+ boolean(), #session{}) ->
{tls_record:tls_version(), ssl:session_id(),
ssl_record:connection_states(), alpn | npn, binary() | undefined}|
{tls_record:tls_version(), {resumed | new, #session{}},
@@ -117,7 +117,7 @@ client_hello(_Host, _Port, ConnectionStates,
%% values.
hello(#server_hello{server_version = {Major, Minor},
random = <<_:24/binary,Down:8/binary>>},
- #{versions := [{M,N}|_]}, _, _)
+ #{versions := [{M,N}|_]}, _, _, _)
when (M > 3 orelse M =:= 3 andalso N >= 4) andalso %% TLS 1.3 client
(Major =:= 3 andalso Minor =:= 3 andalso %% Negotiating TLS 1.2
Down =:= ?RANDOM_OVERRIDE_TLS12) orelse
@@ -131,7 +131,7 @@ hello(#server_hello{server_version = {Major, Minor},
%% equal to the second value if the ServerHello indicates TLS 1.1 or below.
hello(#server_hello{server_version = {Major, Minor},
random = <<_:24/binary,Down:8/binary>>},
- #{versions := [{M,N}|_]}, _, _)
+ #{versions := [{M,N}|_]}, _, _, _)
when (M =:= 3 andalso N =:= 3) andalso %% TLS 1.2 client
(Major =:= 3 andalso Minor < 3 andalso %% Negotiating TLS 1.1 or prior
Down =:= ?RANDOM_OVERRIDE_TLS11) ->
@@ -157,7 +157,7 @@ hello(#server_hello{server_version = LegacyVersion,
#server_hello_selected_version{selected_version = Version} = HelloExt}
},
#{versions := SupportedVersions} = SslOpt,
- ConnectionStates0, Renegotiation) ->
+ ConnectionStates0, Renegotiation, OldId) ->
%% In TLS 1.3, the TLS server indicates its version using the "supported_versions" extension
%% (Section 4.2.1), and the legacy_version field MUST be set to 0x0303, which is the version
%% number for TLS 1.2.
@@ -171,10 +171,11 @@ hello(#server_hello{server_version = LegacyVersion,
true ->
case Version of
{3,3} ->
+ IsNew = ssl_session:is_new(OldId, SessionId),
%% TLS 1.2 ServerHello with "supported_versions" (special case)
handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
Compression, HelloExt, SslOpt,
- ConnectionStates0, Renegotiation);
+ ConnectionStates0, Renegotiation, IsNew);
SelectedVersion ->
%% TLS 1.3
{next_state, wait_sh, SelectedVersion}
@@ -191,17 +192,30 @@ hello(#server_hello{server_version = Version,
session_id = SessionId,
extensions = HelloExt},
#{versions := SupportedVersions} = SslOpt,
- ConnectionStates0, Renegotiation) ->
+ ConnectionStates0, Renegotiation, OldId) ->
+ IsNew = ssl_session:is_new(OldId, SessionId),
case tls_record:is_acceptable_version(Version, SupportedVersions) of
true ->
handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
Compression, HelloExt, SslOpt,
- ConnectionStates0, Renegotiation);
+ ConnectionStates0, Renegotiation, IsNew);
false ->
?ALERT_REC(?FATAL, ?PROTOCOL_VERSION)
- end;
+ end.
+%%--------------------------------------------------------------------
+-spec hello(#client_hello{}, ssl_options(),
+ ssl_record:connection_states() | {inet:port_number(), #session{}, db_handle(),
+ atom(), ssl_record:connection_states(),
+ binary() | undefined, ssl:kex_algo()},
+ boolean()) ->
+ {tls_record:tls_version(), ssl:session_id(),
+ ssl_record:connection_states(), alpn | npn, binary() | undefined}|
+ {tls_record:tls_version(), {resumed | new, #session{}},
+ ssl_record:connection_states(), binary() | undefined,
+ HelloExt::map(), {ssl:hash(), ssl:sign_algo()} |
+ undefined} | {atom(), atom()} | {atom(), atom(), tuple()} | #alert{}.
%% TLS 1.2 Server
%% - If "supported_versions" is present (ClientHello):
%% - Select version from "supported_versions" (ignore ClientHello.legacy_version)
@@ -338,7 +352,8 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites,
try ssl_handshake:handle_client_hello_extensions(tls_record, Random, CipherSuites,
HelloExt, Version, SslOpts,
Session0, ConnectionStates0,
- Renegotiation) of
+ Renegotiation,
+ Session0#session.is_resumable) of
{Session, ConnectionStates, Protocol, ServerHelloExt} ->
{Version, {Type, Session}, ConnectionStates, Protocol,
ServerHelloExt, HashSign}
@@ -348,11 +363,11 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites,
handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
- Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation) ->
+ Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation, IsNew) ->
try ssl_handshake:handle_server_hello_extensions(tls_record, Random, CipherSuite,
Compression, HelloExt, Version,
SslOpt, ConnectionStates0,
- Renegotiation) of
+ Renegotiation, IsNew) of
{ConnectionStates, ProtoExt, Protocol} ->
{Version, SessionId, ConnectionStates, ProtoExt, Protocol}
catch throw:Alert ->
diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl
index 982d0fa787..3dc01fbcb0 100644
--- a/lib/ssl/src/tls_handshake_1_3.erl
+++ b/lib/ssl/src/tls_handshake_1_3.erl
@@ -117,14 +117,22 @@ server_hello_random(hello_retry_request, _) ->
?HELLO_RETRY_REQUEST_RANDOM.
-encrypted_extensions(#state{handshake_env = #handshake_env{alpn = undefined}}) ->
- #encrypted_extensions{
- extensions = #{}
- };
-encrypted_extensions(#state{handshake_env = #handshake_env{alpn = ALPNProtocol}}) ->
- Extensions = ssl_handshake:add_alpn(#{}, ALPNProtocol),
+encrypted_extensions(#state{handshake_env = HandshakeEnv}) ->
+ E0 = #{},
+ E1 = case HandshakeEnv#handshake_env.alpn of
+ undefined ->
+ E0;
+ ALPNProtocol ->
+ ssl_handshake:add_alpn(#{}, ALPNProtocol)
+ end,
+ E2 = case HandshakeEnv#handshake_env.max_frag_enum of
+ undefined ->
+ E1;
+ MaxFragEnum ->
+ E1#{max_frag_enum => MaxFragEnum}
+ end,
#encrypted_extensions{
- extensions = Extensions
+ extensions = E2
}.
@@ -214,7 +222,7 @@ certificate_verify(PrivateKey, SignatureScheme,
ssl_record:pending_connection_state(ConnectionStates, write),
#security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR,
- {HashAlgo, _, _} =
+ {HashAlgo, SignAlgo, _} =
ssl_cipher:scheme_to_components(SignatureScheme),
Context = lists:reverse(Messages),
@@ -225,7 +233,7 @@ certificate_verify(PrivateKey, SignatureScheme,
%% Digital signatures use the hash function defined by the selected signature
%% scheme.
- case sign(THash, ContextString, HashAlgo, PrivateKey) of
+ case sign(THash, ContextString, HashAlgo, PrivateKey, SignAlgo) of
{ok, Signature} ->
{ok, #certificate_verify_1_3{
algorithm = SignatureScheme,
@@ -487,24 +495,9 @@ certificate_entry(DER) ->
%% 79
%% 00
%% 0101010101010101010101010101010101010101010101010101010101010101
-sign(THash, Context, HashAlgo, #'ECPrivateKey'{} = PrivateKey) ->
+sign(THash, Context, HashAlgo, PrivateKey, SignAlgo) ->
Content = build_content(Context, THash),
- try digitally_signed(Content, HashAlgo, PrivateKey) of
- Signature ->
- {ok, Signature}
- catch
- error:badarg ->
- {error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, badarg)}
- end;
-sign(THash, Context, HashAlgo, PrivateKey) ->
- Content = build_content(Context, THash),
-
- %% The length of the Salt MUST be equal to the length of the output
- %% of the digest algorithm: rsa_pss_saltlen = -1
- try digitally_signed(Content, HashAlgo, PrivateKey,
- [{rsa_padding, rsa_pkcs1_pss_padding},
- {rsa_pss_saltlen, -1},
- {rsa_mgf1_md, HashAlgo}]) of
+ try ssl_handshake:digitally_signed({3,4}, Content, HashAlgo, PrivateKey, SignAlgo) of
Signature ->
{ok, Signature}
catch
@@ -512,25 +505,9 @@ sign(THash, Context, HashAlgo, PrivateKey) ->
{error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, badarg)}
end.
-
-verify(THash, Context, HashAlgo, Signature, {?'id-ecPublicKey', PublicKey, PublicKeyParams}) ->
+verify(THash, Context, HashAlgo, SignAlgo, Signature, PublicKeyInfo) ->
Content = build_content(Context, THash),
- try public_key:verify(Content, HashAlgo, Signature, {PublicKey, PublicKeyParams}) of
- Result ->
- {ok, Result}
- catch
- error:badarg ->
- {error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, badarg)}
- end;
-verify(THash, Context, HashAlgo, Signature, {?rsaEncryption, PublicKey, _PubKeyParams}) ->
- Content = build_content(Context, THash),
-
- %% The length of the Salt MUST be equal to the length of the output
- %% of the digest algorithm: rsa_pss_saltlen = -1
- try public_key:verify(Content, HashAlgo, Signature, PublicKey,
- [{rsa_padding, rsa_pkcs1_pss_padding},
- {rsa_pss_saltlen, -1},
- {rsa_mgf1_md, HashAlgo}]) of
+ try ssl_handshake:verify_signature({3, 4}, Content, {HashAlgo, SignAlgo}, Signature, PublicKeyInfo) of
Result ->
{ok, Result}
catch
@@ -538,7 +515,6 @@ verify(THash, Context, HashAlgo, Signature, {?rsaEncryption, PublicKey, _PubKeyP
{error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, badarg)}
end.
-
build_content(Context, THash) ->
Prefix = binary:copy(<<32>>, 64),
<<Prefix/binary,Context/binary,?BYTE(0),THash/binary>>.
@@ -553,13 +529,14 @@ build_content(Context, THash) ->
do_start(#client_hello{cipher_suites = ClientCiphers,
session_id = SessionId,
extensions = Extensions} = _Hello,
- #state{connection_states = _ConnectionStates0,
+ #state{connection_states = ConnectionStates0,
ssl_options = #{ciphers := ServerCiphers,
signature_algs := ServerSignAlgs,
supported_groups := ServerGroups0,
alpn_preferred_protocols := ALPNPreferredProtocols,
honor_cipher_order := HonorCipherOrder},
session = #session{own_certificate = Cert}} = State0) ->
+
ClientGroups0 = maps:get(elliptic_curves, Extensions, undefined),
ClientGroups = get_supported_groups(ClientGroups0),
ServerGroups = get_supported_groups(ServerGroups0),
@@ -580,6 +557,7 @@ do_start(#client_hello{cipher_suites = ClientCiphers,
{Ref,Maybe} = maybe(),
try
+
%% Handle ALPN extension if ALPN is configured
ALPNProtocol = Maybe(handle_alpn(ALPNPreferredProtocols, ClientALPN)),
@@ -588,17 +566,15 @@ do_start(#client_hello{cipher_suites = ClientCiphers,
%% and a signature algorithm/certificate pair to authenticate itself to
%% the client.
Cipher = Maybe(select_cipher_suite(HonorCipherOrder, ClientCiphers, ServerCiphers)),
-
Groups = Maybe(select_common_groups(ServerGroups, ClientGroups)),
Maybe(validate_client_key_share(ClientGroups, ClientShares)),
-
- {PublicKeyAlgo, SignAlgo, SignHash} = get_certificate_params(Cert),
+ {PublicKeyAlgo, SignAlgo, SignHash, RSAKeySize} = get_certificate_params(Cert),
%% Check if client supports signature algorithm of server certificate
Maybe(check_cert_sign_algo(SignAlgo, SignHash, ClientSignAlgs, ClientSignAlgsCert)),
%% Select signature algorithm (used in CertificateVerify message).
- SelectedSignAlg = Maybe(select_sign_algo(PublicKeyAlgo, ClientSignAlgs, ServerSignAlgs)),
+ SelectedSignAlg = Maybe(select_sign_algo(PublicKeyAlgo, RSAKeySize, ClientSignAlgs, ServerSignAlgs)),
%% Select client public key. If no public key found in ClientShares or
%% ClientShares is empty, trigger HelloRetryRequest as we were able
@@ -609,7 +585,17 @@ do_start(#client_hello{cipher_suites = ClientCiphers,
%% Generate server_share
KeyShare = ssl_cipher:generate_server_share(Group),
- State1 = update_start_state(State0,
+ State1 = 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,
+ connection_states = ConnectionStates1};
+ _ ->
+ State0
+ end,
+
+ State2 = update_start_state(State1,
#{cipher => Cipher,
key_share => KeyShare,
session_id => SessionId,
@@ -624,12 +610,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(State1, ClientPubKey, KeyShare, SessionId)) of
+ case Maybe(send_hello_retry_request(State2, ClientPubKey, KeyShare, SessionId)) of
{_, start} = NextStateTuple ->
NextStateTuple;
{_, negotiated} = NextStateTuple ->
%% Exclude any incompatible PSKs.
- PSK = Maybe(handle_pre_shared_key(State1, OfferedPSKs, Cipher)),
+ PSK = Maybe(handle_pre_shared_key(State2, OfferedPSKs, Cipher)),
Maybe(session_resumption(NextStateTuple, PSK))
end
catch
@@ -933,6 +919,9 @@ do_wait_ee(#encrypted_extensions{extensions = Extensions}, State0) ->
{Ref, Maybe} = maybe(),
try
+ %% RFC 6066: handle received/expected maximum fragment length
+ Maybe(maybe_max_fragment_length(Extensions, State0)),
+
%% Go to state 'wait_finished' if using PSK.
Maybe(maybe_resumption(State0)),
@@ -943,7 +932,9 @@ do_wait_ee(#encrypted_extensions{extensions = Extensions}, State0) ->
{State1, wait_cert_cr}
catch
{Ref, {State, StateName}} ->
- {State, StateName}
+ {State, StateName};
+ {Ref, #alert{} = Alert} ->
+ Alert
end.
@@ -983,6 +974,16 @@ maybe_hello_retry_request(#server_hello{random = ?HELLO_RETRY_REQUEST_RANDOM} =
maybe_hello_retry_request(_, _) ->
ok.
+maybe_max_fragment_length(Extensions, State) ->
+ ServerMaxFragEnum = maps:get(max_frag_enum, Extensions, undefined),
+ ClientMaxFragEnum = ssl_handshake:max_frag_enum(
+ maps:get(max_fragment_length, State#state.ssl_options, undefined)),
+ if ServerMaxFragEnum == ClientMaxFragEnum ->
+ ok;
+ true ->
+ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)}
+ end.
+
maybe_resumption(#state{handshake_env = #handshake_env{resumption = true}} = State) ->
{error, {State, wait_finished}};
@@ -1250,7 +1251,7 @@ process_certificate_request(#certificate_request_1_3{
ServerSignAlgsCert = get_signature_scheme_list(
maps:get(signature_algs_cert, Extensions, undefined)),
- {_PublicKeyAlgo, SignAlgo, SignHash} = get_certificate_params(Cert),
+ {_PublicKeyAlgo, SignAlgo, SignHash, _} = get_certificate_params(Cert),
%% Check if server supports signature algorithm of client certificate
case check_cert_sign_algo(SignAlgo, SignHash, ServerSignAlgs, ServerSignAlgsCert) of
@@ -1850,7 +1851,7 @@ verify_certificate_verify(#state{
ssl_record:pending_connection_state(ConnectionStates, write),
#security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR,
- {HashAlgo, _, _} =
+ {HashAlgo, SignAlg, _} =
ssl_cipher:scheme_to_components(SignatureScheme),
Messages = get_handshake_context_cv(HHistory),
@@ -1864,7 +1865,7 @@ verify_certificate_verify(#state{
%% Digital signatures use the hash function defined by the selected signature
%% scheme.
- case verify(THash, ContextString, HashAlgo, Signature, PublicKeyInfo) of
+ case verify(THash, ContextString, HashAlgo, SignAlg, Signature, PublicKeyInfo) of
{ok, true} ->
{ok, {State0, wait_finished}};
{ok, false} ->
@@ -2026,7 +2027,7 @@ select_cipher_suite(_, [], _) ->
select_cipher_suite(true, ClientCiphers, ServerCiphers) ->
select_cipher_suite(false, ServerCiphers, ClientCiphers);
select_cipher_suite(false, [Cipher|ClientCiphers], ServerCiphers) ->
- case lists:member(Cipher, tls_v1:suites('TLS_v1.3')) andalso
+ case lists:member(Cipher, tls_v1:exclusive_suites(4)) andalso
lists:member(Cipher, ServerCiphers) of
true ->
{ok, Cipher};
@@ -2068,11 +2069,11 @@ check_cert_sign_algo(SignAlgo, SignHash, _, ClientSignAlgsCert) ->
%% DSA keys are not supported by TLS 1.3
-select_sign_algo(dsa, _ClientSignAlgs, _ServerSignAlgs) ->
+select_sign_algo(dsa, _RSAKeySize, _ClientSignAlgs, _ServerSignAlgs) ->
{error, ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_public_key)};
-select_sign_algo(_, [], _) ->
+select_sign_algo(_, _RSAKeySize, [], _) ->
{error, ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm)};
-select_sign_algo(PublicKeyAlgo, [C|ClientSignAlgs], ServerSignAlgs) ->
+select_sign_algo(PublicKeyAlgo, RSAKeySize, [C|ClientSignAlgs], ServerSignAlgs) ->
{_, S, _} = ssl_cipher:scheme_to_components(C),
%% RSASSA-PKCS1-v1_5 and Legacy algorithms are not defined for use in signed
%% TLS handshake messages: filter sha-1 and rsa_pkcs1.
@@ -2082,16 +2083,48 @@ select_sign_algo(PublicKeyAlgo, [C|ClientSignAlgs], ServerSignAlgs) ->
%% RSASSA-PSS PSS algorithms: If the public key is carried in an X.509 certificate,
%% it MUST use the RSASSA-PSS OID.
case ((PublicKeyAlgo =:= rsa andalso S =:= rsa_pss_rsae)
- orelse (PublicKeyAlgo =:= rsa_pss andalso S =:= rsa_pss_pss)
+ orelse (PublicKeyAlgo =:= rsa_pss_pss andalso S =:= rsa_pss_pss)
orelse (PublicKeyAlgo =:= ecdsa andalso S =:= ecdsa))
andalso
lists:member(C, ServerSignAlgs) of
true ->
- {ok, C};
+ validate_key_compatibility(PublicKeyAlgo, RSAKeySize,
+ [C|ClientSignAlgs], ServerSignAlgs);
false ->
- select_sign_algo(PublicKeyAlgo, ClientSignAlgs, ServerSignAlgs)
+ select_sign_algo(PublicKeyAlgo, RSAKeySize, ClientSignAlgs, ServerSignAlgs)
end.
+validate_key_compatibility(PublicKeyAlgo, RSAKeySize, [C|ClientSignAlgs], ServerSignAlgs)
+ when PublicKeyAlgo =:= rsa orelse
+ PublicKeyAlgo =:= rsa_pss_pss ->
+ case is_rsa_key_compatible(RSAKeySize, C) of
+ true ->
+ {ok, C};
+ false ->
+ select_sign_algo(PublicKeyAlgo, RSAKeySize, ClientSignAlgs, ServerSignAlgs)
+ end;
+validate_key_compatibility(_, _, [C|_], _) ->
+ {ok, C}.
+
+is_rsa_key_compatible(KeySize, SigAlg) ->
+ {Hash, _, _} = ssl_cipher:scheme_to_components(SigAlg),
+ HashSize = ssl_cipher:hash_size(Hash),
+
+ %% OpenSSL crypto lib defines a limit on the size of the random salt
+ %% in PSS signatures based on the size of signing RSA key.
+ %% If the limit is unchecked, it causes handshake failures when the
+ %% configured certificates contain short (e.g. 1024-bit) RSA keys.
+ %% For more information see the OpenSSL crypto library
+ %% (rsa_pss:c{77,86}).
+ %% TODO: Move this check into crypto. Investigate if this is a bug in
+ %% OpenSSL crypto lib.
+ if (KeySize < (HashSize + 2)) ->
+ false;
+ (HashSize > (KeySize - HashSize - 2)) ->
+ false;
+ true ->
+ true
+ end.
do_check_cert_sign_algo(_, _, []) ->
{error, ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm)};
@@ -2106,10 +2139,8 @@ do_check_cert_sign_algo(SignAlgo, SignHash, [Scheme|T]) ->
%% id-RSASSA-PSS (rsa_pss) indicates that the key may only be used for PSS signatures.
-%% TODO: Uncomment when rsa_pss signatures are supported in certificates
-%% compare_sign_algos(rsa_pss, Hash, Algo, Hash)
-%% when Algo =:= rsa_pss_pss ->
-%% true;
+compare_sign_algos(rsa_pss_pss, Hash, rsa_pss_pss, Hash) ->
+ true;
%% rsaEncryption (rsa) allows the key to be used for any of the standard encryption or
%% signature schemes.
compare_sign_algos(rsa, Hash, Algo, Hash)
@@ -2121,23 +2152,28 @@ compare_sign_algos(Algo, Hash, Algo, Hash) ->
compare_sign_algos(_, _, _, _) ->
false.
-
get_certificate_params(Cert) ->
- {SignAlgo0, _Param, PublicKeyAlgo0} = ssl_handshake:get_cert_params(Cert),
- {SignHash0, SignAlgo} = public_key:pkix_sign_types(SignAlgo0),
- %% Convert hash to new format
- SignHash = case SignHash0 of
- sha ->
- sha1;
- H -> H
- end,
- PublicKeyAlgo = public_key_algo(PublicKeyAlgo0),
- {PublicKeyAlgo, SignAlgo, SignHash}.
-
-
+ {SignAlgo0, Param, SubjectPublicKeyAlgo0, RSAKeySize} =
+ ssl_handshake:get_cert_params(Cert),
+ {SignHash, SignAlgo} = oids_to_atoms(SignAlgo0, Param),
+ SubjectPublicKeyAlgo = public_key_algo(SubjectPublicKeyAlgo0),
+ {SubjectPublicKeyAlgo, SignAlgo, SignHash, RSAKeySize}.
+
+oids_to_atoms(?'id-RSASSA-PSS', #'RSASSA-PSS-params'{maskGenAlgorithm =
+ #'MaskGenAlgorithm'{algorithm = ?'id-mgf1',
+ parameters = #'HashAlgorithm'{algorithm = HashOid}}}) ->
+ Hash = public_key:pkix_hash_type(HashOid),
+ {Hash, rsa_pss_pss};
+oids_to_atoms(SignAlgo, _) ->
+ case public_key:pkix_sign_types(SignAlgo) of
+ {sha, Sign} ->
+ {sha1, Sign};
+ {_,_} = Algs ->
+ Algs
+ end.
%% Note: copied from ssl_handshake
public_key_algo(?'id-RSASSA-PSS') ->
- rsa_pss;
+ rsa_pss_pss;
public_key_algo(?rsaEncryption) ->
rsa;
public_key_algo(?'id-ecPublicKey') ->
@@ -2411,30 +2447,3 @@ process_user_tickets([H|T], Acc, N) ->
%% (see Section 4.6.1), modulo 2^32.
obfuscate_ticket_age(TicketAge, AgeAdd) ->
(TicketAge + AgeAdd) rem round(math:pow(2,32)).
-
-
-digitally_signed(Msg, HashAlgo, PrivateKey) ->
- digitally_signed(Msg, HashAlgo, PrivateKey, []).
-
-digitally_signed(Msg, HashAlgo, PrivateKey, Options) ->
- try do_digitally_signed(Msg, HashAlgo, PrivateKey, Options) of
- Signature ->
- Signature
- catch
- error:_ ->
- {error, ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, bad_key(PrivateKey))}
- end.
-
-do_digitally_signed(Msg, HashAlgo, #{algorithm := Alg} = Engine, Options) ->
- crypto:sign(Alg, HashAlgo, Msg, maps:remove(algorithm, Engine), Options);
-do_digitally_signed(Msg, HashAlgo, Key, Options) ->
- public_key:sign(Msg, HashAlgo, Key, Options).
-
-bad_key(#'RSAPrivateKey'{}) ->
- unacceptable_rsa_key;
-bad_key(#'ECPrivateKey'{}) ->
- unacceptable_ecdsa_key;
-bad_key(#{algorithm := rsa}) ->
- unacceptable_rsa_key;
-bad_key(#{algorithm := ecdsa}) ->
- unacceptable_ecdsa_key.
diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl
index dfdc0bd50b..8d67be687a 100644
--- a/lib/ssl/src/tls_record.erl
+++ b/lib/ssl/src/tls_record.erl
@@ -33,12 +33,12 @@
-include_lib("kernel/include/logger.hrl").
%% Handling of incoming data
--export([get_tls_records/4, init_connection_states/2]).
+-export([get_tls_records/5, init_connection_states/2]).
%% Encoding TLS records
-export([encode_handshake/3, encode_alert_record/3,
encode_change_cipher_spec/2, encode_data/3]).
--export([encode_plain_text/4, split_iovec/1]).
+-export([encode_plain_text/4, split_iovec/2]).
%% Decoding
-export([decode_cipher_text/4]).
@@ -55,7 +55,8 @@
-export_type([tls_version/0, tls_atom_version/0]).
-type tls_version() :: ssl_record:ssl_version().
--type tls_atom_version() :: sslv3 | tlsv1 | 'tlsv1.1' | 'tlsv1.2'.
+-type tls_atom_version() :: sslv3 | tlsv1 | 'tlsv1.1' | 'tlsv1.2' | 'tlsv1.3'.
+-type tls_max_frag_len() :: undefined | 512 | 1024 | 2048 | 4096.
-compile(inline).
@@ -83,6 +84,7 @@ init_connection_states(Role, BeastMitigation) ->
binary(),
[tls_version()] | tls_version(),
Buffer0 :: binary() | {'undefined' | #ssl_tls{}, {[binary()],non_neg_integer(),[binary()]}},
+ tls_max_frag_len(),
ssl_options()) ->
{Records :: [#ssl_tls{}],
Buffer :: {'undefined' | #ssl_tls{}, {[binary()],non_neg_integer(),[binary()]}}} |
@@ -92,10 +94,10 @@ init_connection_states(Role, BeastMitigation) ->
%% Description: Given old buffer and new data from TCP, packs up a records
%% data
%%--------------------------------------------------------------------
-get_tls_records(Data, Versions, Buffer, SslOpts) when is_binary(Buffer) ->
- parse_tls_records(Versions, {[Data],byte_size(Data),[]}, SslOpts, undefined);
-get_tls_records(Data, Versions, {Hdr, {Front,Size,Rear}}, SslOpts) ->
- parse_tls_records(Versions, {Front,Size + byte_size(Data),[Data|Rear]}, SslOpts, Hdr).
+get_tls_records(Data, Versions, Buffer, MaxFragLen, SslOpts) when is_binary(Buffer) ->
+ parse_tls_records(Versions, {[Data],byte_size(Data),[]}, MaxFragLen, SslOpts, undefined);
+get_tls_records(Data, Versions, {Hdr, {Front,Size,Rear}}, MaxFragLen, SslOpts) ->
+ parse_tls_records(Versions, {Front,Size + byte_size(Data),[Data|Rear]}, MaxFragLen, SslOpts, Hdr).
%%====================================================================
%% Encoding
@@ -112,12 +114,18 @@ encode_handshake(Frag, {3, 4}, ConnectionStates) ->
encode_handshake(Frag, Version,
#{current_write :=
#{beast_mitigation := BeastMitigation,
+ max_fragment_length := MaxFragmentLength,
security_parameters :=
#security_parameters{bulk_cipher_algorithm = BCA}}} =
ConnectionStates) ->
+ MaxLength = if is_integer(MaxFragmentLength) ->
+ MaxFragmentLength;
+ true ->
+ ?MAX_PLAIN_TEXT_LENGTH
+ end,
case iolist_size(Frag) of
- N when N > ?MAX_PLAIN_TEXT_LENGTH ->
- Data = split_iovec(erlang:iolist_to_iovec(Frag), Version, BCA, BeastMitigation),
+ N when N > MaxLength ->
+ Data = split_iovec(erlang:iolist_to_iovec(Frag), Version, BCA, BeastMitigation, MaxLength),
encode_fragments(?HANDSHAKE, Version, Data, ConnectionStates);
_ ->
encode_plain_text(?HANDSHAKE, Version, Frag, ConnectionStates)
@@ -155,10 +163,16 @@ encode_data(Data, {3, 4}, ConnectionStates) ->
tls_record_1_3:encode_data(Data, ConnectionStates);
encode_data(Data, Version,
#{current_write := #{beast_mitigation := BeastMitigation,
+ max_fragment_length := MaxFragmentLength,
security_parameters :=
#security_parameters{bulk_cipher_algorithm = BCA}}} =
ConnectionStates) ->
- Fragments = split_iovec(Data, Version, BCA, BeastMitigation),
+ MaxLength = if is_integer(MaxFragmentLength) ->
+ MaxFragmentLength;
+ true ->
+ ?MAX_PLAIN_TEXT_LENGTH
+ end,
+ Fragments = split_iovec(Data, Version, BCA, BeastMitigation, MaxLength),
encode_fragments(?APPLICATION_DATA, Version, Fragments, ConnectionStates).
%%====================================================================
@@ -442,11 +456,11 @@ hello_version([Highest|_]) when Highest >= {3,3} ->
hello_version(Versions) ->
lowest_protocol_version(Versions).
-split_iovec([]) ->
+split_iovec([], _) ->
[];
-split_iovec(Data) ->
- {Part,Rest} = split_iovec(Data, ?MAX_PLAIN_TEXT_LENGTH, []),
- [Part|split_iovec(Rest)].
+split_iovec(Data, MaximumFragmentLength) ->
+ {Part,Rest} = split_iovec(Data, MaximumFragmentLength, []),
+ [Part|split_iovec(Rest, MaximumFragmentLength)].
%%--------------------------------------------------------------------
%%% Internal functions
@@ -461,7 +475,8 @@ initial_connection_state(ConnectionEnd, BeastMitigation) ->
mac_secret => undefined,
secure_renegotiation => undefined,
client_verify_data => undefined,
- server_verify_data => undefined
+ server_verify_data => undefined,
+ max_fragment_length => undefined
}.
%% Used by logging to recreate the received bytes
@@ -470,88 +485,92 @@ build_tls_record(#ssl_tls{type = Type, version = {MajVer, MinVer}, fragment = Fr
<<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer),?UINT16(Length), Fragment/binary>>.
-parse_tls_records(Versions, Q, SslOpts, undefined) ->
- decode_tls_records(Versions, Q, SslOpts, [], undefined, undefined, undefined);
-parse_tls_records(Versions, Q, SslOpts, #ssl_tls{type = Type, version = Version, fragment = Length}) ->
- decode_tls_records(Versions, Q, SslOpts, [], Type, Version, Length).
+parse_tls_records(Versions, Q, MaxFragLen, SslOpts, undefined) ->
+ decode_tls_records(Versions, Q, MaxFragLen, SslOpts, [], undefined, undefined, undefined);
+parse_tls_records(Versions, Q, MaxFragLen, SslOpts, #ssl_tls{type = Type, version = Version, fragment = Length}) ->
+ decode_tls_records(Versions, Q, MaxFragLen, SslOpts, [], Type, Version, Length).
%% Generic code path
-decode_tls_records(Versions, {_,Size,_} = Q0, SslOpts, Acc, undefined, _Version, _Length) ->
+decode_tls_records(Versions, {_,Size,_} = Q0, MaxFragLen, SslOpts, Acc, undefined, _Version, _Length) ->
if
5 =< Size ->
{<<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer), ?UINT16(Length)>>, Q} = binary_from_front(5, Q0),
- validate_tls_records_type(Versions, Q, SslOpts, Acc, Type, {MajVer,MinVer}, Length);
+ validate_tls_records_type(Versions, Q, MaxFragLen, SslOpts, Acc, Type, {MajVer,MinVer}, Length);
3 =< Size ->
{<<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer)>>, Q} = binary_from_front(3, Q0),
- validate_tls_records_type(Versions, Q, SslOpts, Acc, Type, {MajVer,MinVer}, undefined);
+ validate_tls_records_type(Versions, Q, MaxFragLen, SslOpts, Acc, Type, {MajVer,MinVer}, undefined);
1 =< Size ->
{<<?BYTE(Type)>>, Q} = binary_from_front(1, Q0),
- validate_tls_records_type(Versions, Q, SslOpts, Acc, Type, undefined, undefined);
+ validate_tls_records_type(Versions, Q, MaxFragLen, SslOpts, Acc, Type, undefined, undefined);
true ->
- validate_tls_records_type(Versions, Q0, SslOpts, Acc, undefined, undefined, undefined)
+ validate_tls_records_type(Versions, Q0, MaxFragLen, SslOpts, Acc, undefined, undefined, undefined)
end;
-decode_tls_records(Versions, {_,Size,_} = Q0, SslOpts, Acc, Type, undefined, _Length) ->
+decode_tls_records(Versions, {_,Size,_} = Q0, MaxFragLen, SslOpts, Acc, Type, undefined, _Length) ->
if
4 =< Size ->
{<<?BYTE(MajVer),?BYTE(MinVer), ?UINT16(Length)>>, Q} = binary_from_front(4, Q0),
- validate_tls_record_version(Versions, Q, SslOpts, Acc, Type, {MajVer,MinVer}, Length);
+ validate_tls_record_version(Versions, Q, MaxFragLen, SslOpts, Acc, Type, {MajVer,MinVer}, Length);
2 =< Size ->
{<<?BYTE(MajVer),?BYTE(MinVer)>>, Q} = binary_from_front(2, Q0),
- validate_tls_record_version(Versions, Q, SslOpts, Acc, Type, {MajVer,MinVer}, undefined);
+ validate_tls_record_version(Versions, Q, MaxFragLen, SslOpts, Acc, Type, {MajVer,MinVer}, undefined);
true ->
- validate_tls_record_version(Versions, Q0, SslOpts, Acc, Type, undefined, undefined)
+ validate_tls_record_version(Versions, Q0, MaxFragLen, SslOpts, Acc, Type, undefined, undefined)
end;
-decode_tls_records(Versions, {_,Size,_} = Q0, SslOpts, Acc, Type, Version, undefined) ->
+decode_tls_records(Versions, {_,Size,_} = Q0, MaxFragLen, SslOpts, Acc, Type, Version, undefined) ->
if
2 =< Size ->
{<<?UINT16(Length)>>, Q} = binary_from_front(2, Q0),
- validate_tls_record_length(Versions, Q, SslOpts, Acc, Type, Version, Length);
+ validate_tls_record_length(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length);
true ->
- validate_tls_record_length(Versions, Q0, SslOpts, Acc, Type, Version, undefined)
+ validate_tls_record_length(Versions, Q0, MaxFragLen, SslOpts, Acc, Type, Version, undefined)
end;
-decode_tls_records(Versions, Q, SslOpts, Acc, Type, Version, Length) ->
- validate_tls_record_length(Versions, Q, SslOpts, Acc, Type, Version, Length).
+decode_tls_records(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length) ->
+ validate_tls_record_length(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length).
-validate_tls_records_type(_Versions, Q, _SslOpts, Acc, undefined, _Version, _Length) ->
+validate_tls_records_type(_Versions, Q, _MaxFragLen, _SslOpts, Acc, undefined, _Version, _Length) ->
{lists:reverse(Acc),
{undefined, Q}};
-validate_tls_records_type(Versions, Q, SslOpts, Acc, Type, Version, Length) ->
+validate_tls_records_type(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length) ->
if
?KNOWN_RECORD_TYPE(Type) ->
- validate_tls_record_version(Versions, Q, SslOpts, Acc, Type, Version, Length);
+ validate_tls_record_version(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length);
true ->
%% Not ?KNOWN_RECORD_TYPE(Type)
?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, {unsupported_record_type, Type})
end.
-validate_tls_record_version(_Versions, Q, _SslOpts, Acc, Type, undefined, _Length) ->
+validate_tls_record_version(_Versions, Q, _MaxFragLen, _SslOpts, Acc, Type, undefined, _Length) ->
{lists:reverse(Acc),
{#ssl_tls{type = Type, version = undefined, fragment = undefined}, Q}};
-validate_tls_record_version(Versions, Q, SslOpts, Acc, Type, Version, Length) ->
+validate_tls_record_version(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length) ->
case Versions of
_ when is_list(Versions) ->
case is_acceptable_version(Version, Versions) of
true ->
- validate_tls_record_length(Versions, Q, SslOpts, Acc, Type, Version, Length);
+ validate_tls_record_length(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length);
false ->
?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, {unsupported_version, Version})
end;
{3, 4} when Version =:= {3, 3} ->
- validate_tls_record_length(Versions, Q, SslOpts, Acc, Type, Version, Length);
+ validate_tls_record_length(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length);
Version ->
%% Exact version match
- validate_tls_record_length(Versions, Q, SslOpts, Acc, Type, Version, Length);
+ validate_tls_record_length(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length);
_ ->
?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, {unsupported_version, Version})
end.
-validate_tls_record_length(_Versions, Q, _SslOpts, Acc, Type, Version, undefined) ->
+validate_tls_record_length(_Versions, Q, _MaxFragLen, _SslOpts, Acc, Type, Version, undefined) ->
{lists:reverse(Acc),
{#ssl_tls{type = Type, version = Version, fragment = undefined}, Q}};
-validate_tls_record_length(Versions, {_,Size0,_} = Q0,
+validate_tls_record_length(Versions, {_,Size0,_} = Q0, MaxFragLen,
#{log_level := LogLevel} = SslOpts,
Acc, Type, Version, Length) ->
- Max = max_len(Versions),
+ Max = if is_integer(MaxFragLen) ->
+ MaxFragLen + ?MAX_PADDING_LENGTH + ?MAX_MAC_LENGTH;
+ true ->
+ max_len(Versions)
+ end,
if
Length =< Max ->
if
@@ -560,7 +579,7 @@ validate_tls_record_length(Versions, {_,Size0,_} = Q0,
{Fragment, Q} = binary_from_front(Length, Q0),
Record = #ssl_tls{type = Type, version = Version, fragment = Fragment},
ssl_logger:debug(LogLevel, inbound, 'record', Record),
- decode_tls_records(Versions, Q, SslOpts, [Record|Acc], undefined, undefined, undefined);
+ decode_tls_records(Versions, Q, MaxFragLen, SslOpts, [Record|Acc], undefined, undefined, undefined);
true ->
{lists:reverse(Acc),
{#ssl_tls{type = Type, version = Version, fragment = Length}, Q0}}
@@ -669,20 +688,20 @@ encode_fragments(_Type, _Version, _Data, CS, _CompS, _CipherS, _Seq, _CipherFrag
%% 1/n-1 splitting countermeasure Rizzo/Duong-Beast, RC4 chiphers are
%% not vulnerable to this attack.
-split_iovec(Data, Version, BCA, one_n_minus_one)
+split_iovec(Data, Version, BCA, one_n_minus_one, MaxLength)
when (BCA =/= ?RC4) andalso ({3, 1} == Version orelse
{3, 0} == Version) ->
{Part, RestData} = split_iovec(Data, 1, []),
- [Part|split_iovec(RestData)];
+ [Part|split_iovec(RestData, MaxLength)];
%% 0/n splitting countermeasure for clients that are incompatible with 1/n-1
%% splitting.
-split_iovec(Data, Version, BCA, zero_n)
+split_iovec(Data, Version, BCA, zero_n, MaxLength)
when (BCA =/= ?RC4) andalso ({3, 1} == Version orelse
{3, 0} == Version) ->
{Part, RestData} = split_iovec(Data, 0, []),
- [Part|split_iovec(RestData)];
-split_iovec(Data, _Version, _BCA, _BeatMitigation) ->
- split_iovec(Data).
+ [Part|split_iovec(RestData, MaxLength)];
+split_iovec(Data, _Version, _BCA, _BeatMitigation, MaxLength) ->
+ split_iovec(Data, MaxLength).
split_iovec([Bin|Data] = Bin_Data, SplitSize, Acc) ->
BinSize = byte_size(Bin),
diff --git a/lib/ssl/src/tls_record_1_3.erl b/lib/ssl/src/tls_record_1_3.erl
index 89f2a484ff..a9ba415099 100644
--- a/lib/ssl/src/tls_record_1_3.erl
+++ b/lib/ssl/src/tls_record_1_3.erl
@@ -43,11 +43,17 @@
%
%% Description: Encodes a handshake message to send on the tls-1.3-socket.
%%--------------------------------------------------------------------
-encode_handshake(Frag, ConnectionStates) ->
+encode_handshake(Frag, #{current_write := #{max_fragment_length := MaxFragmentLength}} =
+ ConnectionStates) ->
+ MaxLength = if is_integer(MaxFragmentLength) ->
+ MaxFragmentLength;
+ true ->
+ %% TODO: Consider padding here
+ ?MAX_PLAIN_TEXT_LENGTH
+ end,
case iolist_size(Frag) of
- N when N > ?MAX_PLAIN_TEXT_LENGTH ->
- %% TODO: Consider padding here
- Data = tls_record:split_iovec(Frag),
+ N when N > MaxLength ->
+ Data = tls_record:split_iovec(erlang:iolist_to_iovec(Frag), MaxLength),
encode_iolist(?HANDSHAKE, Data, ConnectionStates);
_ ->
encode_plain_text(?HANDSHAKE, Frag, ConnectionStates)
@@ -69,8 +75,14 @@ encode_alert_record(#alert{level = Level, description = Description},
%%
%% Description: Encodes data to send on the ssl-socket.
%%--------------------------------------------------------------------
-encode_data(Frag, ConnectionStates) ->
- Data = tls_record:split_iovec(Frag),
+encode_data(Frag, #{current_write := #{max_fragment_length := MaxFragmentLength}} =
+ ConnectionStates) ->
+ MaxLength = if is_integer(MaxFragmentLength) ->
+ MaxFragmentLength;
+ true ->
+ ?MAX_PLAIN_TEXT_LENGTH
+ end,
+ Data = tls_record:split_iovec(Frag, MaxLength),
encode_iolist(?APPLICATION_DATA, Data, ConnectionStates).
encode_plain_text(Type, Data0, #{current_write := Write0} = ConnectionStates) ->
diff --git a/lib/ssl/src/tls_server_session_ticket.erl b/lib/ssl/src/tls_server_session_ticket.erl
index a804f81eaa..9517cc5afd 100644
--- a/lib/ssl/src/tls_server_session_ticket.erl
+++ b/lib/ssl/src/tls_server_session_ticket.erl
@@ -81,8 +81,8 @@ init(Args) ->
handle_call({new_session_ticket, Prf, MasterSecret}, _From,
#state{nonce = Nonce,
lifetime = LifeTime,
- stateful = #{}} = State0) ->
- Id = stateful_psk_id(),
+ stateful = #{id_generator := IdGen}} = State0) ->
+ Id = stateful_psk_ticket_id(IdGen),
PSK = tls_v1:pre_shared_key(MasterSecret, ticket_nonce(Nonce), Prf),
SessionTicket = new_session_ticket(Id, Nonce, LifeTime),
State = stateful_ticket_store(Id, SessionTicket, Prf, PSK, State0),
@@ -166,7 +166,8 @@ inital_state([stateful, Lifetime, TicketStoreSize|_]) ->
nonce = 0,
stateful = #{db => stateful_store(),
max => TicketStoreSize,
- ref_index => #{}
+ ref_index => #{},
+ id_generator => crypto:strong_rand_bytes(16)
}
}.
@@ -295,8 +296,13 @@ stateful_living_ticket({TimeStamp,_},
Lived < LifeTime.
-stateful_psk_id() ->
- term_to_binary(make_ref()).
+stateful_psk_ticket_id(Key) ->
+ Unique = erlang:unique_integer(),
+ %% Obfuscate to avoid DoS attack possiblities
+ %% that could invalidate tickets and render them
+ %% unusable. This id should be unpredictable
+ %% and unique but have no other cryptographic requirements.
+ crypto:crypto_one_time(aes_128_ecb, Key, <<Unique:128>>, true).
%%%===================================================================
%%% Stateless ticket
diff --git a/lib/ssl/src/tls_v1.erl b/lib/ssl/src/tls_v1.erl
index 8237886f27..8e6807d0ab 100644
--- a/lib/ssl/src/tls_v1.erl
+++ b/lib/ssl/src/tls_v1.erl
@@ -29,22 +29,52 @@
-include("ssl_internal.hrl").
-include("ssl_record.hrl").
--export([master_secret/4, finished/5, certificate_verify/3, mac_hash/7, hmac_hash/3,
- setup_keys/8, suites/1, prf/5,
- ecc_curves/1, ecc_curves/2, oid_to_enum/1, enum_to_oid/1,
- default_signature_algs/1, signature_algs/2,
- default_signature_schemes/1, signature_schemes/2,
- groups/1, groups/2, group_to_enum/1, enum_to_group/1, default_groups/1]).
-
--export([derive_secret/4, hkdf_expand_label/5, hkdf_extract/3, hkdf_expand/4,
- key_schedule/3, key_schedule/4, create_info/3,
- external_binder_key/2, resumption_binder_key/2,
- client_early_traffic_secret/3, early_exporter_master_secret/3,
- client_handshake_traffic_secret/3, server_handshake_traffic_secret/3,
- client_application_traffic_secret_0/3, server_application_traffic_secret_0/3,
- exporter_master_secret/3, resumption_master_secret/3,
- update_traffic_secret/2, calculate_traffic_keys/3,
- transcript_hash/2, finished_key/2, finished_verify_data/3, pre_shared_key/3]).
+-export([master_secret/4,
+ finished/5,
+ certificate_verify/3,
+ mac_hash/7,
+ hmac_hash/3,
+ setup_keys/8,
+ suites/1,
+ exclusive_suites/1,
+ prf/5,
+ ecc_curves/1,
+ ecc_curves/2,
+ oid_to_enum/1,
+ enum_to_oid/1,
+ default_signature_algs/1,
+ signature_algs/2,
+ default_signature_schemes/1,
+ signature_schemes/2,
+ groups/1,
+ groups/2,
+ group_to_enum/1,
+ enum_to_group/1,
+ default_groups/1]).
+
+-export([derive_secret/4,
+ hkdf_expand_label/5,
+ hkdf_extract/3,
+ hkdf_expand/4,
+ key_schedule/3,
+ key_schedule/4,
+ create_info/3,
+ external_binder_key/2,
+ resumption_binder_key/2,
+ client_early_traffic_secret/3,
+ early_exporter_master_secret/3,
+ client_handshake_traffic_secret/3,
+ server_handshake_traffic_secret/3,
+ client_application_traffic_secret_0/3,
+ server_application_traffic_secret_0/3,
+ exporter_master_secret/3,
+ resumption_master_secret/3,
+ update_traffic_secret/2,
+ calculate_traffic_keys/3,
+ transcript_hash/2,
+ finished_key/2,
+ finished_verify_data/3,
+ pre_shared_key/3]).
-type named_curve() :: sect571r1 | sect571k1 | secp521r1 | brainpoolP512r1 |
sect409k1 | sect409r1 | brainpoolP384r1 | secp384r1 |
@@ -453,7 +483,7 @@ mac_hash(Method, Mac_write_secret, Seq_num, Type, {Major, Minor},
%% TODO 1.3 same as above?
--spec suites(1|2|3|4|'TLS_v1.3') -> [ssl_cipher_format:cipher_suite()].
+-spec suites(1|2|3|4) -> [ssl_cipher_format:cipher_suite()].
suites(Minor) when Minor == 1; Minor == 2 ->
[
@@ -472,31 +502,57 @@ suites(Minor) when Minor == 1; Minor == 2 ->
?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA
];
suites(3) ->
+ exclusive_suites(3) ++ suites(2);
+
+suites(4) ->
+ exclusive_suites(4) ++ suites(3).
+
+exclusive_suites(4) ->
+ [?TLS_AES_256_GCM_SHA384,
+ ?TLS_AES_128_GCM_SHA256,
+ ?TLS_CHACHA20_POLY1305_SHA256,
+ ?TLS_AES_128_CCM_SHA256,
+ ?TLS_AES_128_CCM_8_SHA256
+ ];
+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_CBC_SHA384,
?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
+
?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_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_128_GCM_SHA256,
?TLS_DHE_DSS_WITH_AES_128_GCM_SHA256,
+
+ ?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
+
?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,
?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256
@@ -505,25 +561,24 @@ suites(3) ->
%% ?TLS_DH_DSS_WITH_AES_256_GCM_SHA384,
%% ?TLS_DH_RSA_WITH_AES_128_GCM_SHA256,
%% ?TLS_DH_DSS_WITH_AES_128_GCM_SHA256
- ] ++ suites(2);
-
-suites(4) ->
- [?TLS_AES_256_GCM_SHA384,
- ?TLS_AES_128_GCM_SHA256,
- ?TLS_CHACHA20_POLY1305_SHA256,
- ?TLS_AES_128_CCM_SHA256,
- ?TLS_AES_128_CCM_8_SHA256
- ] ++ 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,
-suites('TLS_v1.3') ->
- [?TLS_AES_256_GCM_SHA384,
- ?TLS_AES_128_GCM_SHA256,
- ?TLS_CHACHA20_POLY1305_SHA256,
- ?TLS_AES_128_CCM_SHA256,
- ?TLS_AES_128_CCM_8_SHA256
+ ?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) ->
signature_algs({3, 3}, HashSigns);
signature_algs({3, 3}, HashSigns) ->
diff --git a/lib/ssl/test/Makefile b/lib/ssl/test/Makefile
index f4324f2f13..27b7598991 100644
--- a/lib/ssl/test/Makefile
+++ b/lib/ssl/test/Makefile
@@ -54,6 +54,7 @@ MODULES = \
ssl_npn_SUITE \
openssl_npn_SUITE\
openssl_sni_SUITE\
+ ssl_mfl_SUITE \
ssl_renegotiate_SUITE\
openssl_renegotiate_SUITE\
openssl_reject_SUITE\
diff --git a/lib/ssl/test/openssl_alpn_SUITE.erl b/lib/ssl/test/openssl_alpn_SUITE.erl
index fc18d053aa..409c90b0a8 100644
--- a/lib/ssl/test/openssl_alpn_SUITE.erl
+++ b/lib/ssl/test/openssl_alpn_SUITE.erl
@@ -129,7 +129,7 @@ init_per_testcase(TestCase, Config) ->
special_init(erlang_client_alpn_openssl_server_alpn_renegotiate, Config) ->
{ok, Version} = application:get_env(ssl, protocol_version),
- case ssl_test_lib:check_sane_openssl_renegotaite(Config, Version) of
+ case ssl_test_lib:check_sane_openssl_renegotiate(Config, Version) of
{skip, _} = Skip ->
Skip;
Config ->
@@ -137,7 +137,7 @@ special_init(erlang_client_alpn_openssl_server_alpn_renegotiate, Config) ->
end;
special_init(erlang_server_alpn_openssl_client_alpn_renegotiate, Config) ->
{ok, Version} = application:get_env(ssl, protocol_version),
- case ssl_test_lib:check_sane_openssl_renegotaite(Config, Version) of
+ case ssl_test_lib:check_sane_openssl_renegotiate(Config, Version) of
{skip, _} = Skip ->
Skip;
Config ->
@@ -299,7 +299,7 @@ start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, Callba
{from, self()},
{mfa, {ssl_test_lib,
erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}},
- {options, [{reuse_sessions, false} | ClientOpts]}]),
+ {options, ClientOpts}]),
Callback(Client, OpensslPort),
@@ -368,7 +368,7 @@ start_erlang_client_and_openssl_server_for_alpn_npn_negotiation(Config, Data, Ca
{from, self()},
{mfa, {ssl_test_lib,
erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}},
- {options, [{reuse_sessions, false} | ClientOpts]}]),
+ {options, ClientOpts}]),
Callback(Client, OpensslPort),
diff --git a/lib/ssl/test/openssl_cipher_suite_SUITE.erl b/lib/ssl/test/openssl_cipher_suite_SUITE.erl
index 048fbb1e92..5246cc028e 100644
--- a/lib/ssl/test/openssl_cipher_suite_SUITE.erl
+++ b/lib/ssl/test/openssl_cipher_suite_SUITE.erl
@@ -27,7 +27,7 @@
-include_lib("common_test/include/ct.hrl").
--define(DEFAULT_TIMEOUT, {seconds, 30}).
+-define(DEFAULT_TIMEOUT, {seconds, 6}).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
diff --git a/lib/ssl/test/openssl_client_cert_SUITE.erl b/lib/ssl/test/openssl_client_cert_SUITE.erl
index d04ba601cf..3c97822915 100644
--- a/lib/ssl/test/openssl_client_cert_SUITE.erl
+++ b/lib/ssl/test/openssl_client_cert_SUITE.erl
@@ -48,6 +48,8 @@ groups() ->
{dsa, [], all_version_tests()},
{rsa_1_3, [], all_version_tests() ++ tls_1_3_tests() ++ [unsupported_sign_algo_client_auth,
unsupported_sign_algo_cert_client_auth]},
+ {rsa_pss_rsae, [], all_version_tests() ++ tls_1_3_tests()},
+ {rsa_pss_pss, [], all_version_tests() ++ tls_1_3_tests()},
{ecdsa_1_3, [], all_version_tests() ++ tls_1_3_tests()}
].
@@ -71,6 +73,8 @@ pre_tls_1_3_protocol_groups() ->
tls_1_3_protocol_groups() ->
[{group, rsa_1_3},
+ {group, rsa_pss_rsae},
+ {group, rsa_pss_pss},
{group, ecdsa_1_3}].
tls_1_3_tests() ->
@@ -139,6 +143,28 @@ init_per_group(Group, Config0) when Group == rsa;
[] ->
{skip, {no_sup, Group, Version}}
end;
+init_per_group(Alg, Config) when Alg == rsa_pss_rsae;
+ Alg == rsa_pss_pss ->
+ Supports = crypto:supports(),
+ RSAOpts = proplists:get_value(rsa_opts, Supports),
+
+ case lists:member(rsa_pkcs1_pss_padding, RSAOpts)
+ andalso lists:member(rsa_pss_saltlen, RSAOpts)
+ andalso lists:member(rsa_mgf1_md, RSAOpts)
+ andalso ssl_test_lib:is_sane_oppenssl_pss(Alg)
+ of
+ true ->
+ #{client_config := COpts,
+ server_config := SOpts} = ssl_test_lib:make_rsa_pss_pem(Alg, [], Config, ""),
+ [{cert_key_alg, Alg} |
+ lists:delete(cert_key_alg,
+ [{client_cert_opts, COpts},
+ {server_cert_opts, SOpts} |
+ lists:delete(server_cert_opts,
+ lists:delete(client_cert_opts, Config))])];
+ false ->
+ {skip, "Missing crypto or OpenSSL support"}
+ end;
init_per_group(Group, Config0) when Group == ecdsa;
Group == ecdsa_1_3 ->
PKAlg = crypto:supports(public_keys),
diff --git a/lib/ssl/test/openssl_npn_SUITE.erl b/lib/ssl/test/openssl_npn_SUITE.erl
index 11b2e46358..a37a4bf1f6 100644
--- a/lib/ssl/test/openssl_npn_SUITE.erl
+++ b/lib/ssl/test/openssl_npn_SUITE.erl
@@ -88,30 +88,10 @@ end_per_suite(_Config) ->
ssl_test_lib:kill_openssl().
init_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- case ssl_test_lib:supports_ssl_tls_version(GroupName) of
- true ->
- case ssl_test_lib:check_sane_openssl_version(GroupName) of
- true ->
- ssl_test_lib:init_tls_version(GroupName, Config);
- false ->
- {skip, openssl_does_not_support_version}
- end;
- false ->
- {skip, openssl_does_not_support_version}
- end;
- _ ->
- Config
- end.
+ ssl_test_lib:init_per_group_openssl(GroupName, Config).
end_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- ssl_test_lib:clean_tls_version(Config);
- false ->
- Config
- end.
+ ssl_test_lib:end_per_group(GroupName, Config).
init_per_testcase(TestCase, Config) ->
ct:timetrap({seconds, 10}),
@@ -119,10 +99,10 @@ init_per_testcase(TestCase, Config) ->
special_init(erlang_client_openssl_server_npn_renegotiate, Config) ->
{ok, Version} = application:get_env(ssl, protocol_version),
- ssl_test_lib:check_sane_openssl_renegotaite(Config, Version);
+ ssl_test_lib:check_sane_openssl_renegotiate(Config, Version);
special_init(erlang_server_openssl_client_npn_renegotiate, Config) ->
{ok, Version} = application:get_env(ssl, protocol_version),
- case ssl_test_lib:check_sane_openssl_renegotaite(Config, Version) of
+ case ssl_test_lib:check_sane_openssl_renegotiate(Config, Version) of
Config ->
ssl_test_lib:openssl_allows_client_renegotiate(Config);
Skip ->
diff --git a/lib/ssl/test/openssl_reject_SUITE.erl b/lib/ssl/test/openssl_reject_SUITE.erl
index a637035f3c..d451f8437e 100644
--- a/lib/ssl/test/openssl_reject_SUITE.erl
+++ b/lib/ssl/test/openssl_reject_SUITE.erl
@@ -74,30 +74,10 @@ end_per_suite(_Config) ->
ssl_test_lib:kill_openssl().
init_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- case ssl_test_lib:supports_ssl_tls_version(GroupName) of
- true ->
- case ssl_test_lib:check_sane_openssl_version(GroupName) of
- true ->
- ssl_test_lib:init_tls_version(GroupName, Config);
- false ->
- {skip, openssl_does_not_support_version}
- end;
- false ->
- {skip, openssl_does_not_support_version}
- end;
- _ ->
- Config
- end.
+ ssl_test_lib:init_per_group_openssl(GroupName, Config).
end_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- ssl_test_lib:clean_tls_version(Config);
- false ->
- Config
- end.
+ ssl_test_lib:end_per_group(GroupName, Config).
init_per_testcase(TestCase, Config) ->
ct:timetrap({seconds, 10}),
diff --git a/lib/ssl/test/openssl_renegotiate_SUITE.erl b/lib/ssl/test/openssl_renegotiate_SUITE.erl
index 78cd4446fc..66dfdc8115 100644
--- a/lib/ssl/test/openssl_renegotiate_SUITE.erl
+++ b/lib/ssl/test/openssl_renegotiate_SUITE.erl
@@ -96,32 +96,20 @@ end_per_suite(_Config) ->
ssl_test_lib:kill_openssl().
init_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
+ case ssl_test_lib:check_sane_openssl_version(GroupName) of
true ->
- case ssl_test_lib:supports_ssl_tls_version(GroupName) of
- true ->
- case ssl_test_lib:check_sane_openssl_version(GroupName) of
- true ->
- ssl_test_lib:check_sane_openssl_renegotiate(
- ssl_test_lib:init_tls_version(GroupName, Config),
- GroupName);
- false ->
- {skip, openssl_does_not_support_version}
- end;
- false ->
- {skip, openssl_does_not_support_version}
- end;
- _ ->
- Config
+ case ssl_test_lib:check_sane_openssl_renegotiate(Config, GroupName) of
+ {skip,_} = Skip ->
+ Skip;
+ _ ->
+ ssl_test_lib:init_per_group_openssl(GroupName, Config)
+ end;
+ false ->
+ {skip, {atom_to_list(GroupName) ++ " not supported by OpenSSL"}}
end.
-
end_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- ssl_test_lib:clean_tls_version(Config);
- false ->
- Config
- end.
+ ssl_test_lib:end_per_group(GroupName, Config).
+
init_per_testcase(erlang_client_openssl_server_nowrap_seqnum, Config) ->
ct:timetrap(?DEFAULT_TIMEOUT),
ssl_test_lib:openssl_allows_client_renegotiate(Config);
diff --git a/lib/ssl/test/openssl_server_cert_SUITE.erl b/lib/ssl/test/openssl_server_cert_SUITE.erl
index 9d8e095460..805da27510 100644
--- a/lib/ssl/test/openssl_server_cert_SUITE.erl
+++ b/lib/ssl/test/openssl_server_cert_SUITE.erl
@@ -49,6 +49,8 @@ groups() ->
%% TODO: Create proper conf of openssl server
%%++ [unsupported_sign_algo_client_auth,
%% unsupported_sign_algo_cert_client_auth]},
+ {rsa_pss_rsae, [], all_version_tests() ++ tls_1_3_tests()},
+ {rsa_pss_pss, [], all_version_tests() ++ tls_1_3_tests()},
{ecdsa_1_3, [], all_version_tests() ++ tls_1_3_tests()}
].
@@ -72,6 +74,8 @@ pre_tls_1_3_protocol_groups() ->
tls_1_3_protocol_groups() ->
[{group, rsa_1_3},
+ {group, rsa_pss_rsae},
+ {group, rsa_pss_pss},
{group, ecdsa_1_3}].
tls_1_3_tests() ->
@@ -151,6 +155,28 @@ init_per_group(rsa_1_3 = Group, Config0) ->
[] ->
{skip, {no_sup, Group, Version}}
end;
+init_per_group(Alg, Config) when Alg == rsa_pss_rsae;
+ Alg == rsa_pss_pss ->
+ Supports = crypto:supports(),
+ RSAOpts = proplists:get_value(rsa_opts, Supports),
+
+ case lists:member(rsa_pkcs1_pss_padding, RSAOpts)
+ andalso lists:member(rsa_pss_saltlen, RSAOpts)
+ andalso lists:member(rsa_mgf1_md, RSAOpts)
+ andalso ssl_test_lib:is_sane_oppenssl_pss(Alg)
+ of
+ true ->
+ #{client_config := COpts,
+ server_config := SOpts} = ssl_test_lib:make_rsa_pss_pem(Alg, [], Config, ""),
+ [{cert_key_alg, Alg} |
+ lists:delete(cert_key_alg,
+ [{client_cert_opts, COpts},
+ {server_cert_opts, SOpts} |
+ lists:delete(server_cert_opts,
+ lists:delete(client_cert_opts, Config))])];
+ false ->
+ {skip, "Missing crypto or OpenSSL support"}
+ end;
init_per_group(ecdsa = Group, Config0) ->
PKAlg = crypto:supports(public_keys),
case lists:member(ecdsa, PKAlg) andalso (lists:member(ecdh, PKAlg) orelse
diff --git a/lib/ssl/test/openssl_session_SUITE.erl b/lib/ssl/test/openssl_session_SUITE.erl
index e528936eef..ae66cdeb51 100644
--- a/lib/ssl/test/openssl_session_SUITE.erl
+++ b/lib/ssl/test/openssl_session_SUITE.erl
@@ -28,6 +28,7 @@
-define(SLEEP, 1000).
-define(EXPIRE, 10).
+-define(TIMEOUT, {seconds, 120}).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
@@ -93,31 +94,10 @@ end_per_suite(_Config) ->
ssl_test_lib:kill_openssl().
init_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- case ssl_test_lib:supports_ssl_tls_version(GroupName) of
- true ->
- case ssl_test_lib:check_sane_openssl_version(GroupName) of
- true ->
- ssl_test_lib:init_tls_version(GroupName, Config);
- false ->
- {skip, openssl_does_not_support_version}
- end;
- false ->
- {skip, openssl_does_not_support_version}
- end;
- _ ->
- ssl:start(),
- Config
- end.
+ ssl_test_lib:init_per_group_openssl(GroupName, Config).
end_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- ssl_test_lib:clean_tls_version(Config);
- false ->
- Config
- end.
+ ssl_test_lib:end_per_group(GroupName, Config).
init_per_testcase(reuse_session_erlang_client, Config) ->
ct:timetrap(?EXPIRE * 1000 * 5),
@@ -132,17 +112,17 @@ init_per_testcase(reuse_session_erlang_server, Config) ->
true ->
case ssl_test_lib:openssl_sane_dtls_session_reuse() of
true ->
- ct:timetrap({seconds, 10}),
+ ct:timetrap(?TIMEOUT),
Config;
false ->
{skip, "Broken OpenSSL DTLS session reuse"}
end;
false ->
- ct:timetrap({seconds, 10}),
+ ct:timetrap(?TIMEOUT),
Config
end;
-init_per_testcase(TestCase, Config) ->
- ct:timetrap({seconds, 10}),
+init_per_testcase(_TestCase, Config) ->
+ ct:timetrap(?TIMEOUT),
Config.
end_per_testcase(reuse_session_erlang_client, Config) ->
diff --git a/lib/ssl/test/openssl_sni_SUITE.erl b/lib/ssl/test/openssl_sni_SUITE.erl
index 3010eabf4e..070d94245e 100644
--- a/lib/ssl/test/openssl_sni_SUITE.erl
+++ b/lib/ssl/test/openssl_sni_SUITE.erl
@@ -214,11 +214,13 @@ send_and_hostname(SSLSocket) ->
end.
openssl_client_args(Version, Hostname, Port) ->
- ["s_client", "-connect", Hostname ++ ":" ++ integer_to_list(Port), ssl_test_lib:version_flag(Version)].
+ ssl_test_lib:maybe_force_ipv4(["s_client", "-connect", Hostname ++ ":" ++ integer_to_list(Port),
+ ssl_test_lib:version_flag(Version)]).
openssl_client_args(Version, Hostname, Port, ServerName) ->
- ["s_client", "-connect", Hostname ++ ":" ++
- integer_to_list(Port), ssl_test_lib:version_flag(Version), "-servername", ServerName].
+ ssl_test_lib:maybe_force_ipv4(["s_client", "-connect", Hostname ++ ":" ++
+ integer_to_list(Port),
+ ssl_test_lib:version_flag(Version), "-servername", ServerName]).
check_openssl_sni_support(Config) ->
HelpText = os:cmd("openssl s_client --help"),
diff --git a/lib/ssl/test/ssl_alpn_SUITE.erl b/lib/ssl/test/ssl_alpn_SUITE.erl
index 6fb08671c1..424776293a 100644
--- a/lib/ssl/test/ssl_alpn_SUITE.erl
+++ b/lib/ssl/test/ssl_alpn_SUITE.erl
@@ -61,7 +61,6 @@ alpn_tests() ->
client_alpn_and_server_alpn,
client_alpn_and_server_no_support,
client_no_support_and_server_alpn,
- client_alpn_npn_and_server_alpn,
client_renegotiate,
session_reused
].
@@ -69,7 +68,8 @@ alpn_tests() ->
alpn_npn_coexist() ->
[
client_alpn_npn_and_server_alpn_npn,
- client_alpn_and_server_alpn_npn
+ client_alpn_and_server_alpn_npn,
+ client_alpn_npn_and_server_alpn
].
diff --git a/lib/ssl/test/ssl_api_SUITE.erl b/lib/ssl/test/ssl_api_SUITE.erl
index a80363227f..9856c5db0f 100644
--- a/lib/ssl/test/ssl_api_SUITE.erl
+++ b/lib/ssl/test/ssl_api_SUITE.erl
@@ -43,9 +43,14 @@ all() ->
groups() ->
[
- {'tlsv1.3', [], ((gen_api_tests() ++ tls13_group() ++ handshake_paus_tests()) --
- [dh_params, honor_server_cipher_order, honor_client_cipher_order,
- new_options_in_handshake, handshake_continue_tls13_client])
+ {'tlsv1.3', [], ((gen_api_tests() ++ tls13_group() ++
+ handshake_paus_tests()) --
+ [dh_params,
+ honor_server_cipher_order,
+ honor_client_cipher_order,
+ new_options_in_handshake,
+ handshake_continue_tls13_client,
+ invalid_options])
++ (since_1_2() -- [conf_signature_algs])},
{'tlsv1.2', [], gen_api_tests() ++ since_1_2() ++ handshake_paus_tests() ++ pre_1_3()},
{'tlsv1.1', [], gen_api_tests() ++ handshake_paus_tests() ++ pre_1_3()},
@@ -68,7 +73,8 @@ since_1_2() ->
pre_1_3() ->
[
- default_reject_anonymous
+ default_reject_anonymous,
+ connection_information_with_srp
].
gen_api_tests() ->
[
@@ -140,10 +146,10 @@ tls13_group() ->
client_options_negative_dependency_stateless,
client_options_negative_dependency_role,
server_options_negative_version_gap,
- server_options_negative_dependency_role
+ server_options_negative_dependency_role,
+ invalid_options_tls13
].
-
init_per_suite(Config0) ->
catch crypto:stop(),
try crypto:start() of
@@ -197,6 +203,14 @@ init_per_testcase(handshake_continue_tls13_client, Config) ->
false ->
{skip, "Missing crypto support: TLS 1.3 not supported"}
end;
+init_per_testcase(connection_information_with_srp, Config) ->
+ PKAlg = proplists:get_value(public_keys, crypto:supports()),
+ case lists:member(srp, PKAlg) of
+ true ->
+ Config;
+ false ->
+ {skip, "Missing SRP crypto support"}
+ end;
init_per_testcase(_TestCase, Config) ->
ssl_test_lib:ct_log_supported_protocol_versions(Config),
ct:timetrap({seconds, 10}),
@@ -304,6 +318,56 @@ connection_information(Config) when is_list(Config) ->
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
+
+%%--------------------------------------------------------------------
+connection_information_with_srp() ->
+ [{doc,"Test the result of API function ssl:connection_information/1"
+ "includes srp_username."}].
+connection_information_with_srp(Config) when is_list(Config) ->
+ run_conn_info_srp_test(srp_anon, 'aes_128_cbc', Config).
+
+run_conn_info_srp_test(Kex, Cipher, Config) ->
+ Version = ssl_test_lib:protocol_version(Config),
+ TestCiphers = ssl_test_lib:test_ciphers(Kex, Cipher, Version),
+
+ case TestCiphers of
+ [] ->
+ {skip, {not_sup, Kex, Cipher, Version}};
+ [TestCipher | _T] ->
+ do_run_conn_info_srp_test(TestCipher, Version, Config)
+ end.
+
+do_run_conn_info_srp_test(ErlangCipherSuite, Version, Config) ->
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ SOpts = [{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, undefined}}],
+ COpts = [{srp_identity, {"Test-User", "secret"}}],
+
+ ServerOpts = ssl_test_lib:ssl_options(SOpts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(COpts, Config),
+
+ ct:log("Erlang Cipher Suite is: ~p~n", [ErlangCipherSuite]),
+
+ Server = ssl_test_lib:start_server([
+ {node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, check_srp_in_connection_information, [<<"Test-User">>, server]}},
+ {options, [{versions, [Version]}, {ciphers, [ErlangCipherSuite]} |
+ ServerOpts]}]),
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client(
+ [{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, check_srp_in_connection_information, [<<"Test-User">>, client]}},
+ {options, [{versions, [Version]}, {ciphers, [ErlangCipherSuite]} |
+ ClientOpts]}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
%%--------------------------------------------------------------------
secret_connection_info() ->
@@ -1554,7 +1618,6 @@ invalid_options(Config) when is_list(Config) ->
{verify, 4},
{verify_fun, function},
{fail_if_no_peer_cert, 0},
- {verify_client_once, 1},
{depth, four},
{certfile, 'cert.pem'},
{keyfile,'key.pem' },
@@ -1572,6 +1635,20 @@ invalid_options(Config) when is_list(Config) ->
{active, trice},
{key, 'key.pem' }],
+ TestOpts2 =
+ [{[{anti_replay, '10k'}],
+ %% anti_replay is a server only option but tested with client
+ %% for simplicity
+ {options,dependency,{anti_replay,{versions,['tlsv1.3']}}}},
+ {[{supported_groups, []}],
+ {options,dependency,{supported_groups,{versions,['tlsv1.3']}}}},
+ {[{use_ticket, [<<1,2,3,4>>]}],
+ {options,dependency,{use_ticket,{versions,['tlsv1.3']}}}},
+ {[{verify, verify_none}, {fail_if_no_peer_cert, true}],
+ {options, incompatible,
+ {verify, verify_none},
+ {fail_if_no_peer_cert, true}}}],
+
[begin
Server =
ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0},
@@ -1585,7 +1662,13 @@ invalid_options(Config) when is_list(Config) ->
Check(Client, Server, TestOpt),
ok
end || TestOpt <- TestOpts],
+
+ [begin
+ start_client_negative(Config, TestOpt, ErrorMsg),
+ ok
+ end || {TestOpt, ErrorMsg} <- TestOpts2],
ok.
+
%%-------------------------------------------------------------------
default_reject_anonymous()->
@@ -1853,6 +1936,103 @@ getstat(Config) when is_list(Config) ->
{options, ClientOpts}]),
ssl_test_lib:check_result(Server, ok, Client, ok).
+invalid_options_tls13() ->
+ [{doc, "Test invalid options with TLS 1.3"}].
+invalid_options_tls13(Config) when is_list(Config) ->
+ TestOpts =
+ [{{beast_mitigation, one_n_minus_one},
+ {options, dependency,
+ {beast_mitigation,{versions,[tlsv1]}}},
+ common},
+
+ {{next_protocols_advertised, [<<"http/1.1">>]},
+ {options, dependency,
+ {next_protocols_advertised,
+ {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ server},
+
+ {{client_preferred_next_protocols,
+ {client, [<<"http/1.1">>]}},
+ {options, dependency,
+ {client_preferred_next_protocols,
+ {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ client},
+
+ {{client_renegotiation, false},
+ {options, dependency,
+ {client_renegotiation,
+ {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ server
+ },
+
+ {{padding_check, false},
+ {options, dependency,
+ {padding_check,{versions,[tlsv1]}}},
+ common},
+
+ {{psk_identity, "Test-User"},
+ {options, dependency,
+ {psk_identity,{versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ common},
+
+ {{user_lookup_fun,
+ {fun ssl_test_lib:user_lookup/3, <<1,2,3>>}},
+ {options, dependency,
+ {user_lookup_fun,{versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ common},
+
+ {{reuse_session, fun(_,_,_,_) -> false end},
+ {options, dependency,
+ {reuse_session,
+ {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ server},
+
+ {{reuse_session, <<1,2,3,4>>},
+ {options, dependency,
+ {reuse_session,
+ {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ client},
+
+ {{reuse_sessions, true},
+ {options, dependency,
+ {reuse_sessions,
+ {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ common},
+
+ {{secure_renegotiate, false},
+ {options, dependency,
+ {secure_renegotiate,
+ {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ common},
+
+ {{srp_identity, false},
+ {options, dependency,
+ {srp_identity,
+ {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ client}
+ ],
+
+ Fun = fun(Option, ErrorMsg, Type) ->
+ case Type of
+ server ->
+ start_server_negative(Config,
+ [Option,{versions, ['tlsv1.3']}],
+ ErrorMsg);
+ client ->
+ start_client_negative(Config,
+ [Option,{versions, ['tlsv1.3']}],
+ ErrorMsg);
+ common ->
+ start_server_negative(Config,
+ [Option,{versions, ['tlsv1.3']}],
+ ErrorMsg),
+ start_client_negative(Config,
+ [Option,{versions, ['tlsv1.3']}],
+ ErrorMsg)
+ end
+ end,
+ [Fun(Option, ErrorMsg, Type) || {Option, ErrorMsg, Type} <- TestOpts].
+
%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
@@ -1875,7 +2055,18 @@ 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).
-
+
+check_srp_in_connection_information(_Socket, _Username, client) ->
+ ok;
+check_srp_in_connection_information(Socket, Username, server) ->
+ {ok, Info} = ssl:connection_information(Socket),
+ ct:log("Info ~p~n", [Info]),
+ case proplists:get_value(srp_username, Info, not_found) of
+ Username ->
+ ok;
+ not_found ->
+ ct:fail(srp_username_not_found)
+ end.
%% In TLS 1.3 the master_secret field is used to store multiple secrets from the key schedule and it is a tuple.
%% client_random and server_random are not used in the TLS 1.3 key schedule.
diff --git a/lib/ssl/test/ssl_app_env_SUITE.erl b/lib/ssl/test/ssl_app_env_SUITE.erl
index 498bed2573..d337dabb69 100644
--- a/lib/ssl/test/ssl_app_env_SUITE.erl
+++ b/lib/ssl/test/ssl_app_env_SUITE.erl
@@ -25,7 +25,7 @@
-compile(export_all).
-include_lib("common_test/include/ct.hrl").
-include_lib("ssl/src/ssl_api.hrl").
-
+-define(TIMEOUT, {seconds, 5}).
-define(SLEEP, 500).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
@@ -94,14 +94,14 @@ init_per_testcase(internal_active_1, Config) ->
application:set_env(ssl, internal_active_n, 1),
ssl_test_lib:set_protocol_versions(Version),
ssl:start(),
- ct:timetrap({seconds, 5}),
+ ct:timetrap(?TIMEOUT),
ssl_test_lib:ct_log_supported_protocol_versions(Config),
Config;
init_per_testcase(protocol_versions, Config) ->
Version = ssl_test_lib:protocol_version(Config),
ssl_test_lib:set_protocol_versions(Version),
ssl_test_lib:ct_log_supported_protocol_versions(Config),
- ct:timetrap({seconds, 5}),
+ ct:timetrap(?TIMEOUT),
Config;
init_per_testcase(empty_protocol_versions, Config) ->
ssl:stop(),
@@ -111,10 +111,10 @@ init_per_testcase(empty_protocol_versions, Config) ->
application:set_env(ssl, dtls_protocol_version, []),
ssl:start(),
ssl_test_lib:ct_log_supported_protocol_versions(Config),
- ct:timetrap({seconds, 5}),
+ ct:timetrap(?TIMEOUT),
Config;
init_per_testcase(_TestCase, Config) ->
- ct:timetrap({seconds, 5}),
+ ct:timetrap(?TIMEOUT),
Config.
end_per_testcase(_, Config) ->
diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl
index 18c984d76b..2ca6c7de3d 100644
--- a/lib/ssl/test/ssl_basic_SUITE.erl
+++ b/lib/ssl/test/ssl_basic_SUITE.erl
@@ -125,7 +125,7 @@ appup(Config) when is_list(Config) ->
version_option() ->
[{doc, "Use version option and do no specify ciphers list. Bug specified incorrect ciphers"}].
version_option(Config) when is_list(Config) ->
- Versions = proplists:get_value(default_supported, ssl:versions()),
+ Versions = proplists:get_value(supported, ssl:versions()),
[version_option_test(Config, Version) || Version <- Versions].
%%--------------------------------------------------------------------
@@ -174,13 +174,13 @@ connect_twice(Config) when is_list(Config) ->
defaults(Config) when is_list(Config)->
Versions = ssl:versions(),
false = lists:member(sslv3, proplists:get_value(available, Versions)),
- false = lists:member(sslv3, proplists:get_value(default_supported, Versions)),
+ false = lists:member(sslv3, proplists:get_value(supported, Versions)),
true = lists:member('tlsv1', proplists:get_value(available, Versions)),
- false = lists:member('tlsv1', proplists:get_value(default_supported, Versions)),
+ false = lists:member('tlsv1', proplists:get_value(supported, Versions)),
true = lists:member('tlsv1.1', proplists:get_value(available, Versions)),
- false = lists:member('tlsv1.1', proplists:get_value(default_supported, Versions)),
+ false = lists:member('tlsv1.1', proplists:get_value(supported, Versions)),
true = lists:member('tlsv1.2', proplists:get_value(available, Versions)),
- true = lists:member('tlsv1.2', proplists:get_value(default_supported, Versions)),
+ true = lists:member('tlsv1.2', proplists:get_value(supported, Versions)),
false = lists:member({rsa,rc4_128,sha}, ssl:cipher_suites()),
true = lists:member({rsa,rc4_128,sha}, ssl:cipher_suites(all)),
false = lists:member({rsa,des_cbc,sha}, ssl:cipher_suites()),
@@ -189,8 +189,8 @@ defaults(Config) when is_list(Config)->
true = lists:member({dhe_rsa,des_cbc,sha}, ssl:cipher_suites(all)),
true = lists:member('dtlsv1.2', proplists:get_value(available_dtls, Versions)),
true = lists:member('dtlsv1', proplists:get_value(available_dtls, Versions)),
- true = lists:member('dtlsv1.2', proplists:get_value(default_supported_dtls, Versions)),
- false = lists:member('dtlsv1', proplists:get_value(default_supported_dtls, Versions)).
+ true = lists:member('dtlsv1.2', proplists:get_value(supported_dtls, Versions)),
+ false = lists:member('dtlsv1', proplists:get_value(supported_dtls, Versions)).
fallback() ->
@@ -421,7 +421,7 @@ tls_versions_option(Config) when is_list(Config) ->
ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
- Supported = proplists:get_value(default_supported, ssl:versions()),
+ Supported = proplists:get_value(supported, ssl:versions()),
Available = proplists:get_value(available, ssl:versions()),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
diff --git a/lib/ssl/test/ssl_cert_SUITE.erl b/lib/ssl/test/ssl_cert_SUITE.erl
index bcf2086cca..2fe470e281 100644
--- a/lib/ssl/test/ssl_cert_SUITE.erl
+++ b/lib/ssl/test/ssl_cert_SUITE.erl
@@ -43,15 +43,20 @@ all() ->
groups() ->
[
{'tlsv1.3', [], tls_1_3_protocol_groups()},
- {'tlsv1.2', [], tls_1_2_protocol_groups()},
+ {'tlsv1.2', [], tls_1_2_protocol_groups() -- [{group,rsa_pss_pss}]},
{'tlsv1.1', [], ssl_protocol_groups()},
{'tlsv1', [], ssl_protocol_groups()},
- {'dtlsv1.2', [], tls_1_2_protocol_groups()},
+ {'dtlsv1.2', [], tls_1_2_protocol_groups() -- [{group,rsa_pss_rsae}, {group,rsa_pss_pss}]},
{'dtlsv1', [], ssl_protocol_groups()},
{rsa, [], all_version_tests() ++ rsa_tests() ++ pre_tls_1_3_rsa_tests()},
{ecdsa, [], all_version_tests()},
{dsa, [], all_version_tests()},
- {rsa_1_3, [], all_version_tests() ++ rsa_tests() ++ tls_1_3_tests() ++ tls_1_3_rsa_tests()},
+ {rsa_1_3, [], all_version_tests() ++ rsa_tests() ++
+ tls_1_3_tests() ++ tls_1_3_rsa_tests() ++ [basic_rsa_1024]},
+ {rsa_pss_rsae, [], all_version_tests() ++ rsa_tests()},
+ {rsa_pss_rsae_1_3, [], all_version_tests() ++ rsa_tests() ++ tls_1_3_tests() ++ tls_1_3_rsa_tests()},
+ {rsa_pss_pss, [], all_version_tests() ++ rsa_tests()},
+ {rsa_pss_pss_1_3, [], all_version_tests() ++ rsa_tests() ++ tls_1_3_tests() ++ tls_1_3_rsa_tests()},
{ecdsa_1_3, [], all_version_tests() ++ tls_1_3_tests()}
].
@@ -62,11 +67,17 @@ ssl_protocol_groups() ->
tls_1_2_protocol_groups() ->
[{group, rsa},
{group, ecdsa},
- {group, dsa}].
+ {group, dsa},
+ {group, rsa_pss_rsae},
+ {group, rsa_pss_pss}
+ ].
tls_1_3_protocol_groups() ->
[{group, rsa_1_3},
- {group, ecdsa_1_3}].
+ {group, ecdsa_1_3},
+ {group, rsa_pss_rsae_1_3},
+ {group, rsa_pss_pss_1_3}
+ ].
tls_1_3_tests() ->
[
@@ -103,6 +114,7 @@ all_version_tests() ->
client_auth_allow_partial_chain,
client_auth_do_not_allow_partial_chain,
client_auth_partial_chain_fun_fail,
+ client_auth_sni,
missing_root_cert_no_auth,
missing_root_cert_auth,
missing_root_cert_auth_user_verify_fun_accept,
@@ -118,7 +130,6 @@ all_version_tests() ->
extended_key_usage_auth,
extended_key_usage_client_auth,
cert_expired,
- client_auth_once,
no_auth_key_identifier_ext,
no_auth_key_identifier_ext_keyEncipherment
].
@@ -140,7 +151,8 @@ end_per_suite(_Config) ->
init_per_group(Group, Config0) when Group == rsa;
Group == rsa_1_3 ->
- Config = ssl_test_lib:make_rsa_cert(Config0),
+ Config1 = ssl_test_lib:make_rsa_cert(Config0),
+ Config = ssl_test_lib:make_rsa_1024_cert(Config1),
COpts = proplists:get_value(client_rsa_opts, Config),
SOpts = proplists:get_value(server_rsa_opts, Config),
[{cert_key_alg, rsa} |
@@ -149,6 +161,30 @@ init_per_group(Group, Config0) when Group == rsa;
{server_cert_opts, SOpts} |
lists:delete(server_cert_opts,
lists:delete(client_cert_opts, Config))])];
+
+init_per_group(Alg, Config) when Alg == rsa_pss_rsae;
+ Alg == rsa_pss_pss;
+ Alg == rsa_pss_rsae_1_3;
+ Alg == rsa_pss_pss_1_3 ->
+
+ Supports = crypto:supports(),
+ RSAOpts = proplists:get_value(rsa_opts, Supports),
+
+ case lists:member(rsa_pkcs1_pss_padding, RSAOpts)
+ andalso lists:member(rsa_pss_saltlen, RSAOpts)
+ andalso lists:member(rsa_mgf1_md, RSAOpts) of
+ true ->
+ #{client_config := COpts,
+ server_config := SOpts} = ssl_test_lib:make_rsa_pss_pem(rsa_alg(Alg), [], Config, ""),
+ [{cert_key_alg, rsa_alg(Alg)} |
+ lists:delete(cert_key_alg,
+ [{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"}
+ end;
init_per_group(Group, Config0) when Group == ecdsa;
Group == ecdsa_1_3 ->
@@ -264,6 +300,12 @@ client_auth_partial_chain_fun_fail(Config) when is_list(Config) ->
ssl_cert_tests:client_auth_partial_chain_fun_fail(Config).
%%--------------------------------------------------------------------
+client_auth_sni() ->
+ ssl_cert_tests:client_auth_sni().
+client_auth_sni(Config) when is_list(Config) ->
+ ssl_cert_tests:client_auth_sni(Config).
+
+%%--------------------------------------------------------------------
missing_root_cert_no_auth() ->
ssl_cert_tests:missing_root_cert_no_auth().
missing_root_cert_no_auth(Config) when is_list(Config) ->
@@ -655,41 +697,6 @@ cert_expired(Config) when is_list(Config) ->
ssl_test_lib:check_client_alert(Server, Client, certificate_expired).
%%--------------------------------------------------------------------
-client_auth_once() ->
- [{doc,"Test server option verify_client_once"}].
-
-client_auth_once(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_cert_opts, Config),
- ServerOpts = ssl_test_lib:ssl_options(server_cert_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, send_recv_result_active, []}},
- {options, [{verify, verify_peer},
- {verify_client_once, true}
- | ServerOpts]}]),
- Port = ssl_test_lib:inet_port(Server),
- Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()},
- {mfa, {ssl_test_lib, send_recv_result_active, []}},
- {options, ClientOpts}]),
-
- ssl_test_lib:check_result(Server, ok, Client0, ok),
- Server ! {listen, {mfa, {ssl_test_lib, send_recv_result_active, []}}},
- ssl_test_lib:close(Client0),
- Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()},
- {mfa, {ssl_test_lib, send_recv_result_active, []}},
- {options, ClientOpts}]),
-
- ssl_test_lib:check_result(Client1, ok, Server, ok),
- ssl_test_lib:close(Server),
- ssl_test_lib:close(Client1).
-
-%%--------------------------------------------------------------------
no_auth_key_identifier_ext() ->
[{doc, "Test cert that does not have authorityKeyIdentifier extension"}].
@@ -815,7 +822,7 @@ unsupported_sign_algo_cert_client_auth(Config) ->
ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config),
ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']},
{verify, verify_peer},
- {signature_algs, [rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pss_rsae_sha256]},
+ {signature_algs, [rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pss_rsae_sha256, rsa_pss_pss_sha256]},
%% Skip rsa_pkcs1_sha256!
{signature_algs_cert, [rsa_pkcs1_sha384, rsa_pkcs1_sha512]},
{fail_if_no_peer_cert, true}|ServerOpts0],
@@ -894,6 +901,19 @@ hello_retry_client_auth_empty_cert_rejected(Config) ->
ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, certificate_required).
%%--------------------------------------------------------------------
+basic_rsa_1024() ->
+ [{doc, "TLS 1.3 (Basic): Test if connection can be established using 1024 bits RSA keys in certificates."}].
+
+basic_rsa_1024(Config) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_1024_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_1024_opts, Config),
+ ServerOpts1 = [{versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0],
+ ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0],
+ ServerOpts = [{verify, verify_peer},
+ {fail_if_no_peer_cert, true} | ServerOpts1],
+ ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
+
+%%--------------------------------------------------------------------
%% Internal functions -----------------------------------------------
%%--------------------------------------------------------------------
two_digits_str(N) when N < 10 ->
@@ -918,3 +938,10 @@ n_version(Version) when Version == 'tlsv1.2';
n_version(Version) when Version == 'dtlsv1.2';
Version == 'dtlsv1' ->
dtls_record:protocol_version(Version).
+
+rsa_alg(rsa_pss_rsae_1_3) ->
+ rsa_pss_rsae;
+rsa_alg(rsa_pss_pss_1_3) ->
+ rsa_pss_pss;
+rsa_alg(Atom) ->
+ Atom.
diff --git a/lib/ssl/test/ssl_cert_tests.erl b/lib/ssl/test/ssl_cert_tests.erl
index c88daa2185..657ccd2079 100644
--- a/lib/ssl/test/ssl_cert_tests.erl
+++ b/lib/ssl/test/ssl_cert_tests.erl
@@ -162,6 +162,39 @@ client_auth_partial_chain_fun_fail(Config) when is_list(Config) ->
ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, unknown_ca).
%%--------------------------------------------------------------------
+client_auth_sni() ->
+ [{doc, "Check that sni check works with user verify_fun"}].
+client_auth_sni(Config) when is_list(Config) ->
+ ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config),
+
+ FunAndState = {fun(valid_peer, {bad_cert, unknown_ca}, UserState) ->
+ {valid_peer, UserState};
+ (_,{bad_cert, _} = Reason, _) ->
+ {fail, Reason};
+ (_,{extension, _}, UserState) ->
+ {unknown, UserState};
+ (_, valid, UserState) ->
+ {valid, UserState};
+ (_, valid_peer, UserState) ->
+ {valid, UserState}
+ end, []},
+
+ ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config),
+ ClientOpts = [{verify, verify_peer}, {verify_fun, FunAndState
+ }, {server_name_indication, "localhost"} | ClientOpts0],
+
+ {ok, ServerCAs} = file:read_file(proplists:get_value(cacertfile, ServerOpts0)),
+ [{_,_,_}, {_, IntermidiateCA, _} | _] = public_key:pem_decode(ServerCAs),
+
+ ServerOpts = [{cacerts, [IntermidiateCA]} |
+ proplists:delete(cacertfile, ServerOpts0)],
+ %% Basic test if hostname check is not performed the connection will succeed
+ ssl_test_lib:basic_alert(ClientOpts, ServerOpts0, Config, handshake_failure),
+ %% Also test that user verify_fun is run.
+ %% If user verify fun is not used the ALERT will be unknown_ca
+ ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, handshake_failure).
+
+%%--------------------------------------------------------------------
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_SUITE.erl b/lib/ssl/test/ssl_cipher_suite_SUITE.erl
index 4b19314a7a..71628e9b40 100644
--- a/lib/ssl/test/ssl_cipher_suite_SUITE.erl
+++ b/lib/ssl/test/ssl_cipher_suite_SUITE.erl
@@ -27,6 +27,8 @@
-include_lib("common_test/include/ct.hrl").
+-define(TIMEOUT, {seconds, 5}).
+
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
@@ -267,7 +269,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(?TIMEOUT),
Config;
_ ->
{skip, "Missing 3DES crypto support"}
@@ -283,7 +285,7 @@ init_per_testcase(TestCase, Config) when TestCase == psk_rc4_128;
SupCiphers = proplists:get_value(ciphers, crypto:supports()),
case lists:member(rc4, SupCiphers) of
true ->
- ct:timetrap({seconds, 5}),
+ ct:timetrap(?TIMEOUT),
Config;
_ ->
{skip, "Missing RC4 crypto support"}
@@ -296,7 +298,7 @@ init_per_testcase(TestCase, Config) when TestCase == psk_aes_128_ccm_8;
SupCiphers = proplists:get_value(ciphers, crypto:supports()),
case lists:member(aes_128_ccm, SupCiphers) of
true ->
- ct:timetrap({seconds, 5}),
+ ct:timetrap(?TIMEOUT),
Config;
_ ->
{skip, "Missing AES_128_CCM crypto support"}
@@ -309,7 +311,7 @@ init_per_testcase(TestCase, Config) when TestCase == psk_aes_256_ccm_8;
SupCiphers = proplists:get_value(ciphers, crypto:supports()),
case lists:member(aes_256_ccm, SupCiphers) of
true ->
- ct:timetrap({seconds, 5}),
+ ct:timetrap(?TIMEOUT),
Config;
_ ->
{skip, "Missing AES_256_CCM crypto support"}
@@ -321,7 +323,7 @@ init_per_testcase(aes_256_gcm_sha384, Config) ->
(lists:member(sha384, SupHashs))
of
true ->
- ct:timetrap({seconds, 5}),
+ ct:timetrap(?TIMEOUT),
Config;
_ ->
{skip, "Missing AES_256_GCM_SHA384 crypto support"}
@@ -333,7 +335,7 @@ init_per_testcase(aes_128_gcm_sha256, Config) ->
(lists:member(sha256, SupHashs))
of
true ->
- ct:timetrap({seconds, 5}),
+ ct:timetrap(?TIMEOUT),
Config;
_ ->
{skip, "Missing AES_128_GCM_SHA256 crypto support"}
@@ -345,7 +347,7 @@ init_per_testcase(chacha20_poly1305_sha256, Config) ->
(lists:member(sha256, SupHashs))
of
true ->
- ct:timetrap({seconds, 5}),
+ ct:timetrap(?TIMEOUT),
Config;
_ ->
{skip, "Missing chacha20_poly1305_sha256 crypto support"}
@@ -356,7 +358,7 @@ init_per_testcase(aes_128_ccm_sha256, Config) ->
case (lists:member(aes_128_ccm, SupCiphers)) andalso
(lists:member(sha256, SupHashs)) of
true ->
- ct:timetrap({seconds, 5}),
+ ct:timetrap(?TIMEOUT),
Config;
_ ->
{skip, "Missing AES_128_CCM_SHA256 crypto support"}
@@ -367,7 +369,7 @@ init_per_testcase(aes_128_ccm_8_sha256, Config) ->
case (lists:member(aes_128_ccm, SupCiphers)) andalso
(lists:member(sha256, SupHashs)) of
true ->
- ct:timetrap({seconds, 5}),
+ ct:timetrap(?TIMEOUT),
Config;
_ ->
{skip, "Missing AES_128_CCM_8_SHA256 crypto support"}
@@ -377,7 +379,7 @@ init_per_testcase(TestCase, Config) ->
SupCiphers = proplists:get_value(ciphers, crypto:supports()),
case lists:member(Cipher, SupCiphers) of
true ->
- ct:timetrap({seconds, 5}),
+ ct:timetrap(?TIMEOUT),
Config;
_ ->
{skip, {Cipher, SupCiphers}}
diff --git a/lib/ssl/test/ssl_crl_SUITE.erl b/lib/ssl/test/ssl_crl_SUITE.erl
index 8678d43450..5e0da22eb0 100644
--- a/lib/ssl/test/ssl_crl_SUITE.erl
+++ b/lib/ssl/test/ssl_crl_SUITE.erl
@@ -27,6 +27,8 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
+-define(TIMEOUT, {seconds, 30}).
+
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
@@ -159,7 +161,7 @@ init_per_testcase(Case, Config0) ->
{CertOpts, Config} = init_certs(CertDir, idp_crl, Config),
case make_certs:all(DataDir, CertDir, CertOpts) of
{ok, _} ->
- ct:timetrap({seconds, 6}),
+ ct:timetrap(?TIMEOUT),
[{cert_dir, CertDir} | Config];
_ ->
end_per_testcase(Case, Config0),
diff --git a/lib/ssl/test/ssl_mfl_SUITE.erl b/lib/ssl/test/ssl_mfl_SUITE.erl
new file mode 100644
index 0000000000..bcc2b24651
--- /dev/null
+++ b/lib/ssl/test/ssl_mfl_SUITE.erl
@@ -0,0 +1,513 @@
+%%
+-module(ssl_mfl_SUITE).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+-include_lib("common_test/include/ct.hrl").
+
+-define(SLEEP, 500).
+%%--------------------------------------------------------------------
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
+
+all() ->
+ [{group, 'tlsv1.3'},
+ {group, 'tlsv1.2'},
+ {group, 'tlsv1.1'},
+ {group, 'tlsv1'},
+ {group, 'dtlsv1.2'},
+ {group, 'dtlsv1'}].
+
+groups() ->
+ [{'tlsv1.3', [], tls_mfl_1_3()},
+ {'tlsv1.2', [], tls_mfl()},
+ {'tlsv1.1', [], tls_mfl()},
+ {'tlsv1', [], tls_mfl()},
+ {'dtlsv1.2', [], tls_mfl()},
+ {'dtlsv1', [], tls_mfl()}
+ ].
+
+tls_mfl_common() ->
+ [mfl_client_option, mfl_server_option, mfl_openssl_client, mfl_openssl_server, handshake_continue].
+
+tls_mfl() ->
+ tls_mfl_common() ++ [reuse_session, reuse_session_erlang_server, reuse_session_erlang_client].
+
+tls_mfl_1_3() ->
+ tls_mfl_common().
+
+init_per_suite(Config0) ->
+ catch crypto:stop(),
+ try crypto:start() of
+ ok ->
+ ssl_test_lib:clean_start(),
+ ssl:clear_pem_cache(),
+ Config = ssl_test_lib:make_rsa_cert(Config0),
+ ssl_test_lib:cert_options(Config)
+ catch _:_ ->
+ {skip, "Crypto did not start"}
+ end.
+
+end_per_suite(_Config) ->
+ ssl:stop(),
+ application:stop(crypto).
+
+init_per_group(GroupName, Config) ->
+ ssl_test_lib:init_per_group_openssl(GroupName, Config).
+
+end_per_group(GroupName, Config) ->
+ ssl_test_lib:end_per_group(GroupName, Config).
+
+init_per_testcase(TestCase, Config) when TestCase == mfl_openssl_server;
+ TestCase == mfl_openssl_client;
+ TestCase == reuse_session_erlang_server;
+ TestCase == reuse_session_erlang_client ->
+ case os:cmd("openssl version") of
+ %% Max fragmentation support introduced in OpenSSL 1.1.1
+ "OpenSSL 1.1.1" ++ _ = OpenSSLVersion ->
+ case lists:member({protocol, dtls}, Config) of
+ true ->
+ %% Fixed but not yet released https://github.com/openssl/openssl/commit/dfbaef6
+ {skip, "Broken DTLS max fragmentation support in "++OpenSSLVersion};
+ false ->
+ init_per_testcase(all, Config)
+ end;
+ Other ->
+ {skip, "No/unknown max fragmentation support in "++Other}
+ end;
+init_per_testcase(_TestCase, Config) ->
+ ct:timetrap({seconds, 10}),
+ Config.
+
+end_per_testcase(_TestCase, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% Test Cases --------------------------------------------------------
+%%--------------------------------------------------------------------
+
+nyi(Config) when is_list(Config) ->
+ {skip, "NYI"}.
+
+%--------------------------------------------------------------------------------
+%% check max_fragment_length option on the client is accepted
+%% and both sides can successfully send > MFL
+mfl_client_option(Config) when is_list(Config) ->
+ ok = run_mfl_handshake(Config, 512),
+ ok = run_mfl_handshake(Config, 1024),
+ ok = run_mfl_handshake(Config, 2048),
+ ok = run_mfl_handshake(Config, 4096),
+ ok = run_mfl_handshake(Config, undefined),
+ ok.
+
+%--------------------------------------------------------------------------------
+%% check max_fragment_length option on the server is ignored
+%% and both sides can successfully send > 512 bytes
+mfl_server_option(Config) when is_list(Config) ->
+ Data = "mfl_server_options " ++ lists:duplicate(512, $x),
+ run_mfl_handshake(Config, undefined, Data, [], [{max_fragment_length, 512}]).
+
+%--------------------------------------------------------------------------------
+%% check max_fragment_length interworking with openssl server
+mfl_openssl_server(Config) when is_list(Config) ->
+ mfl_openssl_server(512, Config),
+ mfl_openssl_server(2048, Config).
+
+%--------------------------------------------------------------------------------
+%% check max_fragment_length interworking with openssl client
+mfl_openssl_client(Config) when is_list(Config) ->
+ mfl_openssl_client(1024, Config),
+ mfl_openssl_client(4096, Config).
+
+%--------------------------------------------------------------------------------
+%% check max_fragment_length option on the client is accepted and reused
+reuse_session(Config) when is_list(Config) ->
+ MFL = 512,
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config) ++
+ [{max_fragment_length, MFL}],
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+
+ ssl_test_lib:reuse_session(ClientOpts, ServerOpts, Config).
+
+%%--------------------------------------------------------------------
+
+reuse_session_erlang_server(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+
+ {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ MFL = 512,
+ Data = "reuse_session_erlang_server " ++ lists:duplicate(MFL, $r),
+
+ 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}]),
+ Port = ssl_test_lib:inet_port(Server),
+ Version = ssl_test_lib:protocol_version(Config),
+
+ Exe = "openssl",
+ Args = ["s_client", "-connect", ssl_test_lib:hostname_format(Hostname)
+ ++ ":" ++ integer_to_list(Port),
+ "-tlsextdebug", "-4", "-maxfraglen", integer_to_list(MFL),
+ ssl_test_lib:version_flag(Version),
+ "-reconnect"],
+
+ OpensslPort = ssl_test_lib:portable_open_port(Exe, Args),
+
+ run_mfl_openssl(Server, OpensslPort, MFL, Data),
+
+ %% Clean close down! Server needs to be closed first !!
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close_port(OpensslPort).
+
+%%--------------------------------------------------------------------
+
+reuse_session_erlang_client(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
+
+ MFL = 512,
+ Data = "reuse_session_erlang_client " ++ lists:duplicate(MFL, $r),
+ ClientOpts = [{max_fragment_length, 512} | ClientOpts0],
+
+ Version = ssl_test_lib:protocol_version(Config),
+ Port = ssl_test_lib:inet_port(node()),
+ CertFile = proplists:get_value(certfile, ServerOpts),
+ CACertFile = proplists:get_value(cacertfile, ServerOpts),
+ KeyFile = proplists:get_value(keyfile, ServerOpts),
+
+ Exe = "openssl",
+ Args = ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version),
+ "-tlsextdebug", "-cert", CertFile,"-key", KeyFile, "-CAfile", CACertFile],
+
+ OpensslPort = ssl_test_lib:portable_open_port(Exe, Args),
+
+ OpensslProtocol = case proplists:get_value(protocol, Config) of
+ undefined ->
+ tls;
+ ConfigProtocol ->
+ ConfigProtocol
+ end,
+
+ ssl_test_lib:wait_for_openssl_server(Port, OpensslProtocol),
+
+ Client0 =
+ ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib, session_id, []}},
+ {from, self()},
+ {options, [{reuse_sessions, save}, {verify, verify_peer}| ClientOpts]}]),
+
+ SID = receive
+ {Client0, Id0} ->
+ Id0
+ end,
+
+ %% quit s_server's current session so we can interact with the next client
+ true = port_command(OpensslPort, "q\n"),
+ ssl_test_lib:close(Client0),
+
+ flush(),
+
+ Client1 =
+ ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib, session_id, []}},
+ {from, self()}, {options, [{reuse_session, SID} | ClientOpts]}]),
+ receive
+ {Client1, SID} ->
+ ok
+ after ?SLEEP ->
+ ct:fail(session_not_reused)
+ end,
+
+ ErlRecvFun = fun() ->
+ Data = ssl_test_lib:check_active_receive(Client1, Data)
+ end,
+ run_mfl_openssl(Client1, OpensslPort, MFL, Data, ErlRecvFun),
+
+ %% Clean close down! Server needs to be closed first !!
+ ssl_test_lib:close_port(OpensslPort),
+ ssl_test_lib:close(Client1).
+
+%%--------------------------------------------------------------------
+
+handshake_continue(Config) when is_list(Config) ->
+ ok = run_mfl_handshake_continue(Config, 1024),
+ ok = run_mfl_handshake_continue(Config, undefined),
+ ok.
+
+run_mfl_handshake_continue(Config, MFL) ->
+ Data = if is_integer(MFL) ->
+ "hello world" ++ lists:duplicate(MFL, $u);
+ true ->
+ "hello world" ++ lists:duplicate(999, $x)
+ end,
+ ClientExtraOpts = [{handshake, hello}, {max_fragment_length, MFL}],
+ ServerExtraOpts = [{handshake, hello}],
+ ExtraStartOpts = [{continue_options, [{want_ext, self()}]}],
+ MflEnum = mfl_enum(MFL),
+ PostF = fun(Server, Client) ->
+ receive {Server, {ext, ServerExt}} ->
+ ct:log("Server handshake Ext ~p~n", [ServerExt]),
+ MflEnum = maps:get(max_frag_enum, ServerExt, undefined)
+ end,
+ receive {Client, {ext, ClientExt}} ->
+ ct:log("Client handshake Ext ~p~n", [ClientExt]),
+ case maps:get(server_hello_selected_version, ClientExt, undefined) of
+ {3,4} ->
+ %% For TLS 1.3 the ssl {handshake, hello} API is inconsistent:
+ %% the server gets all the extensions CH+EE, but the client only CH
+ ignore;
+ _ ->
+ MflEnum = maps:get(max_frag_enum, ClientExt, undefined)
+ end
+ end,
+ ok
+ end,
+
+ run_mfl_handshake(Config, MFL, Data, ClientExtraOpts, ServerExtraOpts, ExtraStartOpts, ExtraStartOpts, PostF).
+
+%%--------------------------------------------------------------------
+%% Internal functions ------------------------------------------------
+%%--------------------------------------------------------------------
+run_mfl_handshake(Config, MFL) when is_integer(MFL) ->
+ Data = "hello world" ++ lists:duplicate(MFL, $0),
+ ClientExtraOpts = [{max_fragment_length, MFL}],
+ run_mfl_handshake(Config, MFL, Data, ClientExtraOpts, []);
+run_mfl_handshake(Config, MFL) ->
+ Data = "hello world" ++ lists:duplicate(512, $9),
+ ClientExtraOpts = [],
+ run_mfl_handshake(Config, MFL, Data, ClientExtraOpts, []).
+
+run_mfl_handshake(Config, MFL, Data, ClientExtraOpts, ServerExtraOpts) ->
+ run_mfl_handshake(Config, MFL, Data, ClientExtraOpts, ServerExtraOpts, [], [], fun(_,_) -> ok end).
+
+run_mfl_handshake(Config, MFL, Data, ClientExtraOpts, ServerExtraOpts, ClientExtraStartOpts, ServerExtraStartOpts,
+ PostFun) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ClientOpts = ClientExtraOpts ++ ClientOpts0,
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ ServerOpts = ServerExtraOpts ++ ServerOpts0,
+
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, assert_mfl_and_send_first, [MFL, Data]}},
+ {options, ServerOpts} | ServerExtraStartOpts]),
+
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, assert_mfl_and_recv_first, [MFL, Data]}},
+ {options, ClientOpts} | ClientExtraStartOpts]),
+
+ ok = PostFun(Server, Client),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok).
+
+assert_mfl(Socket, undefined) ->
+ InfoMFL = ssl:connection_information(Socket, [max_fragment_length]),
+ ct:log("Connection MFL ~p, Expecting: [] ~n", [InfoMFL]),
+ {ok, []} = InfoMFL;
+assert_mfl(Socket, MFL) ->
+ InfoMFL = ssl:connection_information(Socket, [max_fragment_length]),
+ ct:log("Connection MFL ~p, Expecting: ~p ~n", [InfoMFL, MFL]),
+ {ok, [{max_fragment_length, ConnMFL}]} = InfoMFL,
+ ConnMFL = MFL.
+
+assert_mfl_and_send_first(Socket, MFL, Data) ->
+ assert_mfl(Socket, MFL),
+ ssl_send(Socket, Data),
+ ssl_receive(Socket, "Got it"++lists:reverse(Data)).
+
+assert_mfl_and_recv_first(Socket, MFL, Data) ->
+ assert_mfl(Socket, MFL),
+ ssl_receive(Socket, Data),
+ ssl_send(Socket, lists:reverse(Data)).
+
+ssl_send(Socket, Data) ->
+ ssl:send(Socket, Data).
+
+ssl_receive(Socket, Data) ->
+ ssl_receive(Socket, Data, []).
+
+ssl_receive(Socket, Data, Buffer) ->
+ receive
+ {ssl, Socket, MoreData} ->
+ ct:log("Received ~p~n",[MoreData]),
+ NewBuffer = Buffer ++ MoreData,
+ case NewBuffer of
+ Data ->
+ ssl:send(Socket, "Got it"),
+ ok;
+ _ ->
+ ssl_receive(Socket, Data, NewBuffer)
+ end;
+ Other ->
+ ct:fail({unexpected_message, Other})
+ after 4000 ->
+ ct:fail({did_not_get, Data})
+ end.
+
+%% ------------------------------------------------------------
+mfl_openssl_server(MFL, Config) ->
+ Data = "mfl_openssl_server " ++ lists:duplicate(MFL, $s),
+ Fun = fun(C,S) -> run_mfl_openssl(C, S, MFL, Data) end,
+ ssl_test_lib:start_erlang_client_and_openssl_server_with_opts(Config,
+ [{max_fragment_length, MFL}],
+ ["-tlsextdebug", "-tlsextdebug"],
+ Data, Fun).
+
+%% ------------------------------------------------------------
+mfl_openssl_client(MFL, Config) ->
+ Data = "mfl_openssl_client " ++ lists:duplicate(MFL, $c),
+ Fun = fun(S,C) -> run_mfl_openssl(S, C, MFL, Data) end,
+ ClientArgs = ["-tlsextdebug", "-4", "-maxfraglen", integer_to_list(MFL)],
+ ssl_test_lib:start_erlang_server_and_openssl_client_with_opts(Config,
+ [],
+ ClientArgs,
+ Data, Fun).
+
+%% ------------------------------------------------------------
+run_mfl_openssl(ErlProc, OpenSSL, MFL, Data) ->
+ ErlRecvFun = fun() ->
+ receive
+ {ErlProc, Data} ->
+ ok
+ after 1000 ->
+ flush(true),
+ error(timeout)
+ end
+ end,
+ run_mfl_openssl(ErlProc, OpenSSL, MFL, Data, ErlRecvFun).
+
+run_mfl_openssl(ErlProc, OpenSSL, MFL, Data, ErlRecvFun) ->
+ MFL = get_openssl_max_fragment_length(OpenSSL),
+
+ true = port_command(OpenSSL, Data),
+ ErlRecvFun(),
+
+ ErlProc ! get_socket,
+ ErlSocket = receive
+ {ErlProc, {socket, ErlSocket0}} ->
+ ErlSocket0
+ end,
+ assert_mfl(ErlSocket, MFL),
+
+ RData = lists:reverse(Data),
+ flush(),
+ ssl:send(ErlSocket, RData),
+ RData = ssl_test_lib:active_recv(OpenSSL, length(RData)),
+ ok.
+
+%% ------------------------------------------------------------
+flush() ->
+ flush(false).
+flush(Noisy) ->
+ receive Rx ->
+ if Noisy ->
+ io:format("~p:~p: ~999p~n", [self(), ?FUNCTION_NAME, Rx]);
+ true ->
+ ignore
+ end,
+ flush(Noisy)
+ after 100 ->
+ ok
+ end.
+
+%% ------------------------------------------------------------
+get_openssl_max_fragment_length(Port) ->
+ get_openssl_max_fragment_length(Port, []).
+
+get_openssl_max_fragment_length(Port, Acc) ->
+ receive
+ {Port, {data, Data}} ->
+ get_openssl_max_fragment_length_line(Port, Acc++Data)
+ after 1000 ->
+ error(timeout)
+ end.
+
+get_openssl_max_fragment_length_line(Port, Acc) ->
+ case get_line(Acc) of
+ more ->
+ get_openssl_max_fragment_length(Port, Acc);
+ {"TLS "++TlsInfo, Acc2} ->
+ get_openssl_max_fragment_length_tlsinfo(TlsInfo, Port, Acc2);
+ {_Discard, Acc2} ->
+ get_openssl_max_fragment_length_line(Port, Acc2)
+ end.
+
+get_openssl_max_fragment_length_tlsinfo("client extension "++ExtInfo, Port, Acc) ->
+ get_openssl_max_fragment_length_ext(ExtInfo, Port, Acc);
+get_openssl_max_fragment_length_tlsinfo("server extension "++ExtInfo, Port, Acc) ->
+ get_openssl_max_fragment_length_ext(ExtInfo, Port, Acc);
+get_openssl_max_fragment_length_tlsinfo(_Acc, Port, Acc) ->
+ get_openssl_max_fragment_length_line(Port, Acc).
+
+get_openssl_max_fragment_length_ext("\"max fragment length\" (id=1), len=1"=Ext, Port, Acc) ->
+ case get_line(Acc) of
+ more ->
+ receive
+ {Port, {data, Data}} ->
+ Acc1 = Acc++Data,
+ get_openssl_max_fragment_length_ext(Ext, Port, Acc1)
+ after 1000 ->
+ error(timeout)
+ end;
+ {"0000 - 01 "++_, _} ->
+ 512;
+ {"0000 - 02 "++_, _} ->
+ 1024;
+ {"0000 - 03 "++_, _} ->
+ 2048;
+ {"0000 - 04 "++_, _} ->
+ 4096
+ end;
+get_openssl_max_fragment_length_ext(_Acc, Port, Acc2) ->
+ get_openssl_max_fragment_length_line(Port, Acc2).
+
+
+get_line(Data) ->
+ get_line(Data, []).
+
+get_line([$\r|T], A) ->
+ get_line(T, A);
+get_line([$\n|T], A) ->
+ {lists:reverse(A), T};
+get_line([], _) ->
+ more;
+get_line([H|T], A) ->
+ get_line(T, [H|A]).
+
+
+get_openssl_data(Port, Exp) ->
+ get_openssl_data(Port, Exp, []).
+
+get_openssl_data(_Port, Exp, Exp) ->
+ ok;
+get_openssl_data(Port, Exp, Acc) ->
+ case lists:prefix(Acc, Exp) of
+ true ->
+ receive
+ {Port, {data, Data}} ->
+ get_openssl_data(Port, Exp, Acc++Data)
+ after 1000 ->
+ error(timeout)
+ end;
+ false ->
+ ct:fail({get_openssl_data, {{expected, Exp}, {got, Acc}}})
+ end.
+
+%% RFC 6066
+mfl_enum(512) -> 1;
+mfl_enum(1024) -> 2;
+mfl_enum(2048) -> 3;
+mfl_enum(4096) -> 4;
+mfl_enum(undefined) -> undefined.
diff --git a/lib/ssl/test/ssl_npn_SUITE.erl b/lib/ssl/test/ssl_npn_SUITE.erl
index 94bd21418c..b3c93c19fb 100644
--- a/lib/ssl/test/ssl_npn_SUITE.erl
+++ b/lib/ssl/test/ssl_npn_SUITE.erl
@@ -24,7 +24,7 @@
%% Note: This directive should only be used in test suites.
-compile(export_all).
-include_lib("common_test/include/ct.hrl").
-
+-define(TIMEOUT, {seconds, 5}).
-define(SLEEP, 500).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
@@ -100,7 +100,7 @@ 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()]),
- ct:timetrap({seconds, 10}),
+ ct:timetrap(?TIMEOUT),
Config.
end_per_testcase(_TestCase, Config) ->
diff --git a/lib/ssl/test/ssl_packet_SUITE.erl b/lib/ssl/test/ssl_packet_SUITE.erl
index cc265e5849..2c6f169fd0 100644
--- a/lib/ssl/test/ssl_packet_SUITE.erl
+++ b/lib/ssl/test/ssl_packet_SUITE.erl
@@ -41,7 +41,7 @@
-define(MANY, 1000).
-define(SOME, 50).
--define(BASE_TIMEOUT_SECONDS, 5).
+-define(BASE_TIMEOUT_SECONDS, 20).
-define(SOME_SCALE, 2).
-define(MANY_SCALE, 3).
@@ -2148,7 +2148,7 @@ server_packet_decode(Socket, Packet) ->
{ssl, Socket, Packet} -> ok;
Other1 -> exit({?LINE, Other1})
end,
- ok = ssl:send(Socket, Packet),
+ spawn(fun() -> ssl:send(Socket, Packet) end),
receive
{ssl, Socket, Packet} -> ok;
Other2 -> exit({?LINE, Other2})
diff --git a/lib/ssl/test/ssl_payload_SUITE.erl b/lib/ssl/test/ssl_payload_SUITE.erl
index f771ec8cf9..6b3df7ec3e 100644
--- a/lib/ssl/test/ssl_payload_SUITE.erl
+++ b/lib/ssl/test/ssl_payload_SUITE.erl
@@ -24,9 +24,8 @@
-compile(export_all).
-include_lib("common_test/include/ct.hrl").
-
--define(TIMEOUT, 600000).
-
+-define(TIMEOUT, {seconds, 20}).
+-define(TIMEOUT_LONG, {seconds, 80}).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
@@ -127,7 +126,7 @@ init_per_testcase(TestCase, Config)
"sparc-sun-solaris2.10" ->
{skip,"Will take to long time on an old Sparc"};
_ ->
- ct:timetrap({seconds, 90}),
+ ct:timetrap(?TIMEOUT_LONG),
Config
end;
@@ -140,11 +139,11 @@ init_per_testcase(TestCase, Config)
TestCase == client_echos_passive_chunk_big;
TestCase == client_echos_active_once_big;
TestCase == client_echos_active_big ->
- ct:timetrap({seconds, 60}),
+ ct:timetrap(?TIMEOUT_LONG),
Config;
init_per_testcase(_TestCase, Config) ->
- ct:timetrap({seconds, 15}),
+ ct:timetrap(?TIMEOUT),
Config.
end_per_testcase(_TestCase, Config) ->
@@ -726,7 +725,7 @@ client_active_once_server_close(
send(_Socket, _Data, 0, _) ->
ok;
send(Socket, Data, Count, RecvEcho) ->
- ok = ssl:send(Socket, Data),
+ spawn(fun() -> ssl:send(Socket, Data) end),
RecvEcho(),
send(Socket, Data, Count - 1, RecvEcho).
diff --git a/lib/ssl/test/ssl_session_SUITE.erl b/lib/ssl/test/ssl_session_SUITE.erl
index db51b5a0a0..60e71501fa 100644
--- a/lib/ssl/test/ssl_session_SUITE.erl
+++ b/lib/ssl/test/ssl_session_SUITE.erl
@@ -552,4 +552,4 @@ connection_states(Random) ->
client_random = Random,
server_random = undefined,
exportable = undefined},
- sequence_number => 0,server_verify_data => undefined}}.
+ sequence_number => 0,server_verify_data => undefined,max_fragment_length => undefined}}.
diff --git a/lib/ssl/test/ssl_session_cache_SUITE.erl b/lib/ssl/test/ssl_session_cache_SUITE.erl
index 1bafb5f4d0..f6b527aaf9 100644
--- a/lib/ssl/test/ssl_session_cache_SUITE.erl
+++ b/lib/ssl/test/ssl_session_cache_SUITE.erl
@@ -28,8 +28,7 @@
-include_lib("common_test/include/ct.hrl").
-define(SLEEP, 1000).
--define(TIMEOUT, 60000).
--define(LONG_TIMEOUT, 600000).
+-define(TIMEOUT, {seconds, 20}).
-define(MAX_TABLE_SIZE, 5).
-behaviour(ssl_session_cache_api).
@@ -123,18 +122,18 @@ init_per_testcase(session_cleanup, Config) ->
application:set_env(ssl, session_lifetime, 5),
ssl:start(),
ssl_test_lib:ct_log_supported_protocol_versions(Config),
- ct:timetrap({seconds, 20}),
+ ct:timetrap(?TIMEOUT),
Config;
init_per_testcase(client_unique_session, Config) ->
- ct:timetrap({seconds, 40}),
+ ct:timetrap(?TIMEOUT),
ssl_test_lib:ct_log_supported_protocol_versions(Config),
Config;
init_per_testcase(save_specific_session, Config) ->
Versions = ssl_test_lib:protocol_version(Config),
ssl_test_lib:clean_start(),
ssl_test_lib:set_protocol_versions(Versions),
- ct:timetrap({seconds, 5}),
+ ct:timetrap(?TIMEOUT),
Config;
init_per_testcase(max_table_size, Config) ->
Versions = ssl_test_lib:protocol_version(Config),
@@ -161,7 +160,7 @@ init_customized_session_cache(Type, Config) ->
Config)),
ets:new(ssl_test, [named_table, public, set]),
ets:insert(ssl_test, {type, Type}),
- ct:timetrap({seconds, 20}),
+ ct:timetrap(?TIMEOUT),
Config.
end_per_testcase(session_cache_process_list, Config) ->
@@ -240,16 +239,13 @@ session_cleanup(Config) when is_list(Config) ->
ssl_test_lib:start_client([{node, ClientNode},
{port, Port}, {host, Hostname},
{mfa, {ssl_test_lib, no_result, []}},
- {from, self()}, {options, ClientOpts}]),
+ {from, self()}, {options, [{reuse_sessions, save} | ClientOpts]}]),
SessionInfo =
receive
{Server, Info} ->
Info
end,
- %% Make sure session is registered
- ct:sleep(?SLEEP*2),
-
{status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)),
[_, _,_, _, Prop] = StatusInfo,
State = ssl_test_lib:state(Prop),
diff --git a/lib/ssl/test/ssl_sni_SUITE.erl b/lib/ssl/test/ssl_sni_SUITE.erl
index f14ea5fae3..4660a98ca5 100644
--- a/lib/ssl/test/ssl_sni_SUITE.erl
+++ b/lib/ssl/test/ssl_sni_SUITE.erl
@@ -27,13 +27,15 @@
-include_lib("public_key/include/public_key.hrl").
-include_lib("kernel/include/inet.hrl").
+-define(TIMEOUT, {seconds, 6}).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
all() ->
- [{group, 'tlsv1.2'},
+ [{group, 'tlsv1.3'},
+ {group, 'tlsv1.2'},
{group, 'tlsv1.1'},
{group, 'tlsv1'},
{group, 'dtlsv1.2'},
@@ -42,6 +44,7 @@ all() ->
groups() ->
[
+ {'tlsv1.3', [], sni_tests()},
{'tlsv1.2', [], sni_tests()},
{'tlsv1.1', [], sni_tests()},
{'tlsv1', [], sni_tests()},
@@ -85,26 +88,10 @@ init_per_suite(Config0) ->
{skip, "Crypto did not start"}
end.
init_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- case ssl_test_lib:sufficient_crypto_support(GroupName) of
- true ->
- ssl_test_lib:init_tls_version(GroupName, Config);
- false ->
- {skip, "Missing crypto support"}
- end;
- _ ->
- ssl:start(),
- Config
- end.
+ ssl_test_lib:init_per_group(GroupName, Config).
end_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- ssl_test_lib:clean_tls_version(Config);
- false ->
- Config
- end.
+ ssl_test_lib:end_per_group(GroupName, Config).
end_per_suite(_) ->
ssl:stop(),
@@ -113,12 +100,12 @@ end_per_suite(_) ->
init_per_testcase(customize_hostname_check, Config) ->
ssl_test_lib:ct_log_supported_protocol_versions(Config),
ssl_test_lib:clean_start(),
- ct:timetrap({seconds, 5}),
+ ct:timetrap(?TIMEOUT),
Config;
init_per_testcase(_TestCase, Config) ->
ssl_test_lib:ct_log_supported_protocol_versions(Config),
ct:log("Ciphers: ~p~n ", [ ssl:cipher_suites()]),
- ct:timetrap({seconds, 5}),
+ ct:timetrap(?TIMEOUT),
Config.
end_per_testcase(_TestCase, Config) ->
diff --git a/lib/ssl/test/ssl_socket_SUITE.erl b/lib/ssl/test/ssl_socket_SUITE.erl
index d648f2f9e1..47d3a72988 100644
--- a/lib/ssl/test/ssl_socket_SUITE.erl
+++ b/lib/ssl/test/ssl_socket_SUITE.erl
@@ -25,7 +25,7 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
-
+-define(TIMEOUT, {seconds, 5}).
-define(SLEEP, 500).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
@@ -87,7 +87,7 @@ end_per_group(_GroupName, Config) ->
Config.
init_per_testcase(raw_inet_option, Config) ->
- ct:timetrap({seconds, 5}),
+ ct:timetrap(?TIMEOUT),
case os:type() of
{unix,linux} ->
Config;
@@ -95,7 +95,7 @@ init_per_testcase(raw_inet_option, Config) ->
{skip, "Raw options are platform-specific"}
end;
init_per_testcase(_TestCase, Config) ->
- ct:timetrap({seconds, 5}),
+ ct:timetrap(?TIMEOUT),
Config.
end_per_testcase(_TestCase, Config) ->
diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl
index 4cacc91a04..d93338be15 100644
--- a/lib/ssl/test/ssl_test_lib.erl
+++ b/lib/ssl/test/ssl_test_lib.erl
@@ -95,7 +95,9 @@ init_per_group(GroupName, Config) ->
init_per_group_openssl(GroupName, Config) ->
case is_tls_version(GroupName) andalso sufficient_crypto_support(GroupName) of
true ->
- case check_sane_openssl_version(GroupName) of
+ case check_sane_openssl_version(GroupName)
+ andalso maybe_legacy_tls_version_support(GroupName, Config)
+ of
true ->
[{version, GroupName}|init_tls_version(GroupName, Config)];
false ->
@@ -264,6 +266,9 @@ do_run_server_core(ListenSocket, AcceptSocket, Opts, Transport, Pid) ->
Pid ! {self(), Reason}
end,
do_run_server_core(ListenSocket, AcceptSocket, Opts, Transport, Pid);
+ get_socket ->
+ Pid ! {self(), {socket, AcceptSocket}},
+ do_run_server_core(ListenSocket, AcceptSocket, Opts, Transport, Pid);
listen ->
run_server(ListenSocket, Opts);
{listen, MFA} ->
@@ -311,7 +316,7 @@ connect(ListenSocket, Node, _N, _, Timeout, SslOpts, cancel) ->
ct:log("~p:~p~nssl:handshake@~p ret ~p",[?MODULE,?LINE, Node,Result]),
Result
end;
-connect(ListenSocket, Node, N, _, Timeout, SslOpts, [_|_] =ContOpts) ->
+connect(ListenSocket, Node, N, _, Timeout, SslOpts, [_|_] =ContOpts0) ->
ct:log("ssl:transport_accept(~p)~n", [ListenSocket]),
{ok, AcceptSocket} = ssl:transport_accept(ListenSocket),
ct:log("~p:~p~nssl:handshake(~p,~p,~p)~n", [?MODULE,?LINE, AcceptSocket, SslOpts,Timeout]),
@@ -320,10 +325,21 @@ connect(ListenSocket, Node, N, _, Timeout, SslOpts, [_|_] =ContOpts) ->
{ok, Socket0, Ext} ->
[_|_] = maps:get(sni, Ext),
ct:log("Ext ~p:~n", [Ext]),
- ct:log("~p:~p~nssl:handshake_continue(~p,~p,~p)~n", [?MODULE,?LINE, Socket0, ContOpts,Timeout]),
+ ContOpts = case lists:keytake(want_ext, 1, ContOpts0) of
+ {value, {_, WantExt}, ContOpts1} ->
+ if is_pid(WantExt) ->
+ WantExt ! {self(), {ext, Ext}};
+ true ->
+ ignore
+ end,
+ ContOpts1;
+ _ ->
+ ContOpts0
+ end,
+ ct:log("~p:~p~nssl:handshake_continue(~p,~p,~p)~n", [?MODULE,?LINE, Socket0, ContOpts,Timeout]),
case ssl:handshake_continue(Socket0, ContOpts, Timeout) of
{ok, Socket} ->
- connect(ListenSocket, Node, N-1, Socket, Timeout, SslOpts, ContOpts);
+ connect(ListenSocket, Node, N-1, Socket, Timeout, SslOpts, ContOpts0);
Error ->
ct:log("~p:~p~nssl:handshake_continue@~p ret ~p",[?MODULE,?LINE, Node,Error]),
Error
@@ -376,8 +392,6 @@ start_erlang_client_and_openssl_server_with_opts(Config, ErlangClientOpts, Opens
{ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
- Data = "From openssl to erlang",
-
Port = ssl_test_lib:inet_port(node()),
CaCertFile = proplists:get_value(cacertfile, ServerOpts),
CertFile = proplists:get_value(certfile, ServerOpts),
@@ -815,6 +829,9 @@ client_loop_core(Socket, Pid, Transport) ->
Pid ! {self(), Reason}
end,
client_loop_core(Socket, Pid, Transport);
+ get_socket ->
+ Pid ! {self(), {socket, Socket}},
+ client_loop_core(Socket, Pid, Transport);
close ->
ct:log("~p:~p~nClient closing~n", [?MODULE,?LINE]),
Transport:close(Socket);
@@ -835,9 +852,20 @@ client_cont_loop(_Node, Host, Port, Pid, Transport, Options, cancel, _Opts) ->
Pid ! {connect_failed, Reason}
end;
-client_cont_loop(_Node, Host, Port, Pid, Transport, Options, ContOpts, Opts) ->
+client_cont_loop(_Node, Host, Port, Pid, Transport, Options, ContOpts0, Opts) ->
case Transport:connect(Host, Port, Options) of
- {ok, Socket0, _} ->
+ {ok, Socket0, Ext} ->
+ ContOpts = case lists:keytake(want_ext, 1, ContOpts0) of
+ {value, {_, WantExt}, ContOpts1} ->
+ if is_pid(WantExt) ->
+ WantExt ! {self(), {ext, Ext}};
+ true ->
+ ignore
+ end,
+ ContOpts1;
+ _ ->
+ ContOpts0
+ end,
ct:log("~p:~p~nClient: handshake_continue(~p, ~p, infinity) ~n", [?MODULE, ?LINE, Socket0, ContOpts]),
case Transport:handshake_continue(Socket0, ContOpts) of
{ok, Socket} ->
@@ -1211,6 +1239,18 @@ default_cert_chain_conf() ->
%% Use only default options
[[],[],[]].
+make_rsa_pss_pem(Alg, _UserConf, Config, Suffix) ->
+ DefClientConf = chain_spec(client, Alg, []),
+ DefServerConf = chain_spec(server, Alg, []),
+ CertChainConf = new_format([{client_chain, DefClientConf}, {server_chain, DefServerConf}]),
+ ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), atom_to_list(Alg) ++ Suffix]),
+ ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), atom_to_list(Alg) ++ Suffix]),
+ GenCertData = public_key:pkix_test_data(CertChainConf),
+ Conf = x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),
+ CConf = proplists:get_value(client_config, Conf),
+ SConf = proplists:get_value(server_config, Conf),
+ #{server_config => SConf,
+ client_config => CConf}.
gen_conf(ClientChainType, ServerChainType, UserClient, UserServer) ->
gen_conf(ClientChainType, ServerChainType, UserClient, UserServer, ?DEFAULT_CURVE).
@@ -1287,8 +1327,33 @@ chain_spec(_Role, ecdsa, Curve) ->
chain_spec(_Role, rsa, _) ->
Digest = {digest, appropriate_sha(crypto:supports())},
[[Digest, {key, hardcode_rsa_key(1)}],
- [Digest, {key, hardcode_rsa_key(2)}],
- [Digest, {key, hardcode_rsa_key(3)}]];
+ [Digest, {key, hardcode_rsa_key(2)}],
+ [Digest, {key, hardcode_rsa_key(3)}]];
+chain_spec(_Role, 'rsa-1024', _) ->
+ Digest = {digest, appropriate_sha(crypto:supports())},
+ [[Digest, {key, hardcode_rsa_1024_key(1)}],
+ [Digest, {key, hardcode_rsa_1024_key(2)}],
+ [Digest, {key, hardcode_rsa_1024_key(3)}]];
+chain_spec(client, rsa_pss_rsae, _) ->
+ Digest = {digest, sha256},
+ [[Digest, {rsa_padding, rsa_pss_rsae}, {key, hardcode_rsa_key(1)}],
+ [Digest, {rsa_padding, rsa_pss_rsae}, {key, hardcode_rsa_key(2)}],
+ [Digest, {rsa_padding, rsa_pss_rsae}, {key, hardcode_rsa_key(3)}]];
+chain_spec(server, rsa_pss_rsae, _) ->
+ Digest = {digest, sha256},
+ [[Digest, {rsa_padding, rsa_pss_rsae}, {key, hardcode_rsa_key(4)}],
+ [Digest, {rsa_padding, rsa_pss_rsae}, {key, hardcode_rsa_key(5)}],
+ [Digest, {rsa_padding, rsa_pss_rsae}, {key, hardcode_rsa_key(6)}]];
+chain_spec(client, rsa_pss_pss, _) ->
+ Digest = {digest, sha256},
+ [[Digest, {rsa_padding, rsa_pss_pss}, {key, {hardcode_rsa_key(1), pss_params(sha256)}}],
+ [Digest, {rsa_padding, rsa_pss_pss}, {key, {hardcode_rsa_key(2), pss_params(sha256)}}],
+ [Digest, {rsa_padding, rsa_pss_pss}, {key, {hardcode_rsa_key(3), pss_params(sha256)}}]];
+chain_spec(server, rsa_pss_pss, _) ->
+ Digest = {digest, sha256},
+ [[Digest, {rsa_padding, rsa_pss_pss}, {key, {hardcode_rsa_key(4), pss_params(sha256)}}],
+ [Digest, {rsa_padding, rsa_pss_pss}, {key, {hardcode_rsa_key(5), pss_params(sha256)}}],
+ [Digest, {rsa_padding, rsa_pss_pss}, {key, {hardcode_rsa_key(6), pss_params(sha256)}}]];
chain_spec(_Role, dsa, _) ->
Digest = {digest, appropriate_sha(crypto:supports())},
[[Digest, {key, hardcode_dsa_key(1)}],
@@ -1404,6 +1469,31 @@ make_rsa_cert(Config) ->
false ->
Config
end.
+
+make_rsa_1024_cert(Config) ->
+ CryptoSupport = crypto:supports(),
+ case proplists:get_bool(rsa, proplists:get_value(public_keys, CryptoSupport)) of
+ true ->
+ ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), "rsa-1024"]),
+ ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), "rsa-1024"]),
+ ClientChain = proplists:get_value(client_chain, Config, default_cert_chain_conf()),
+ ServerChain = proplists:get_value(server_chain, Config, default_cert_chain_conf()),
+ CertChainConf = gen_conf('rsa-1024', 'rsa-1024', ClientChain, ServerChain),
+ GenCertData = public_key:pkix_test_data(CertChainConf),
+ [{server_config, ServerConf},
+ {client_config, ClientConf}] =
+ x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),
+ [{server_rsa_1024_opts, [{ssl_imp, new},{reuseaddr, true} | ServerConf]},
+
+ {server_rsa_1024_verify_opts, [{ssl_imp, new}, {reuseaddr, true},
+ {verify, verify_peer} | ServerConf]},
+ {client_rsa_1024_opts, ClientConf},
+ {client_rsa_1024_verify_opts, [{verify, verify_peer} |ClientConf]}
+ | Config];
+ false ->
+ Config
+ end.
+
appropriate_sha(CryptoSupport) ->
Hashes = proplists:get_value(hashs, CryptoSupport),
case lists:member(sha256, Hashes) of
@@ -1696,51 +1786,31 @@ ecc_test_error(COpts, SOpts, CECCOpts, SECCOpts, Config) ->
Client = start_client_ecc_error(erlang, Port, COpts, CECCOpts, Config),
check_server_alert(Server, Client, insufficient_security).
-start_basic_client(openssl, Version, Port, ClientOpts) ->
- Cert = proplists:get_value(certfile, ClientOpts),
- Key = proplists:get_value(keyfile, ClientOpts),
- CA = proplists:get_value(cacertfile, ClientOpts),
- Groups0 = proplists:get_value(groups, ClientOpts),
- Exe = "openssl",
- Args0 = ["s_client", "-verify", "2", "-port", integer_to_list(Port),
- ssl_test_lib:version_flag(Version),
- "-CAfile", CA, "-host", "localhost", "-msg", "-debug"],
- Args1 =
- case Groups0 of
- undefined ->
- Args0;
- G ->
- Args0 ++ ["-groups", G]
- end,
- Args =
- case {Cert, Key} of
- {C, K} when C =:= undefined orelse
- K =:= undefined ->
- Args1;
- {C, K} ->
- Args1 ++ ["-cert", C, "-key", K]
- end,
-
- OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args),
- true = port_command(OpenSslPort, "Hello world"),
- OpenSslPort.
-
start_client(openssl, Port, ClientOpts, Config) ->
Version = ssl_test_lib:protocol_version(Config),
Exe = "openssl",
Ciphers = proplists:get_value(ciphers, ClientOpts, ssl:cipher_suites(default,Version)),
Groups0 = proplists:get_value(groups, ClientOpts),
CertArgs = openssl_cert_options(ClientOpts, client),
+
Exe = "openssl",
Args0 = case Groups0 of
undefined ->
- ["s_client", "-verify", "2", "-port", integer_to_list(Port), cipher_flag(Version),
+ ["s_client",
+ "-verify", "2",
+ "-port", integer_to_list(Port), cipher_flag(Version),
ciphers(Ciphers, Version),
- ssl_test_lib:version_flag(Version)] ++ CertArgs ++ ["-msg", "-debug"];
+ ssl_test_lib:version_flag(Version)]
+ ++ CertArgs
+ ++ ["-msg", "-debug"];
Group ->
- ["s_client", "-verify", "2", "-port", integer_to_list(Port), cipher_flag(Version),
+ ["s_client",
+ "-verify", "2",
+ "-port", integer_to_list(Port), cipher_flag(Version),
ciphers(Ciphers, Version), "-groups", Group,
- ssl_test_lib:version_flag(Version)] ++ CertArgs ++ ["-msg", "-debug"]
+ ssl_test_lib:version_flag(Version)]
+ ++CertArgs
+ ++ ["-msg", "-debug"]
end,
Args = maybe_force_ipv4(Args0),
OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args),
@@ -1796,15 +1866,16 @@ start_server(openssl, ClientOpts, ServerOpts, Config) ->
CertArgs = openssl_cert_options(ServerOpts, server),
Ciphers = proplists:get_value(ciphers, ClientOpts, ssl:cipher_suites(default,Version)),
Groups0 = proplists:get_value(groups, ServerOpts),
+ SigAlgs = proplists:get_value(openssl_sigalgs, Config, undefined),
Args = case Groups0 of
undefined ->
["s_server", "-accept", integer_to_list(Port), cipher_flag(Version),
ciphers(Ciphers, Version),
- ssl_test_lib:version_flag(Version)] ++ CertArgs ++ ["-msg", "-debug"];
+ ssl_test_lib:version_flag(Version)] ++ sig_algs(SigAlgs) ++ CertArgs ++ ["-msg", "-debug"];
Group ->
["s_server", "-accept", integer_to_list(Port), cipher_flag(Version),
ciphers(Ciphers, Version), "-groups", Group,
- ssl_test_lib:version_flag(Version)] ++ CertArgs ++ ["-msg", "-debug"]
+ ssl_test_lib:version_flag(Version)] ++ sig_algs(SigAlgs) ++ CertArgs ++ ["-msg", "-debug"]
end,
OpenSslPort = portable_open_port(Exe, Args),
true = port_command(OpenSslPort, "Hello world"),
@@ -1821,6 +1892,11 @@ start_server(erlang, _, ServerOpts, Config) ->
{options, [{verify, verify_peer}, {versions, Versions} | ServerOpts]}]),
{Server, inet_port(Server)}.
+sig_algs(undefined) ->
+ [];
+sig_algs(SigAlgs) ->
+ ["-sigalgs " ++ SigAlgs].
+
cipher_flag('tlsv1.3') ->
"-ciphersuites";
cipher_flag(_) ->
@@ -2266,6 +2342,39 @@ is_dtls_version('dtlsv1') ->
is_dtls_version(_) ->
false.
+maybe_legacy_tls_version_support(Version, Config0) when
+ Version == 'tlsv1';
+ Version == 'tlsv1.1' ->
+ %% Check if legacy version is supported
+ Config = ssl_test_lib:make_rsa_cert(Config0),
+ ServerOpts = proplists:get_value(server_rsa_opts, Config),
+ Port = ssl_test_lib:inet_port(node()),
+ CaCertFile = proplists:get_value(cacertfile, ServerOpts),
+ CertFile = proplists:get_value(certfile, ServerOpts),
+ KeyFile = proplists:get_value(keyfile, ServerOpts),
+ Exe = "openssl",
+ Args = ["s_server", "-accept",
+ integer_to_list(Port), "-CAfile", CaCertFile,
+ "-cert", CertFile,"-key", KeyFile],
+
+ OpensslPort = ssl_test_lib:portable_open_port(Exe, Args),
+ ssl_test_lib:wait_for_openssl_server(Port, tls),
+
+ case ssl:connect("localhost", Port, [{versions, [Version]}]) of
+ {ok, Socket} ->
+ ssl:close(Socket),
+ close_port(OpensslPort),
+ true;
+ {error, {tls_alert, {protocol_version, _}}} ->
+ close_port(OpensslPort),
+ false
+ end;
+maybe_legacy_tls_version_support('dtlsv1', Config) ->
+ maybe_legacy_tls_version_support('tlsv1.1', Config);
+maybe_legacy_tls_version_support(_, _) ->
+ %% Not a legacy version
+ true.
+
init_tls_version(Version, Config)
when Version == 'dtlsv1.2'; Version == 'dtlsv1' ->
ssl:stop(),
@@ -2470,7 +2579,7 @@ active_recv(Socket, N, Acc) ->
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)*",
- "", [{return, list}]).
+ "", [global,{return, list}]).
active_once_recv(_Socket, 0) ->
ok;
@@ -2561,6 +2670,21 @@ is_sane_oppenssl_client() ->
true
end.
+is_sane_oppenssl_pss(rsa_pss_pss) ->
+ case portable_cmd("openssl",["version"]) of
+ "OpenSSL 1.1.1" ++ Rest ->
+ hd(Rest) >= $c;
+ _ ->
+ false
+ end;
+is_sane_oppenssl_pss(rsa_pss_rsae) ->
+ case portable_cmd("openssl",["version"]) of
+ "OpenSSL 1.1.1" ++ _ ->
+ true;
+ _ ->
+ false
+ end.
+
is_fips(openssl) ->
VersionStr = portable_cmd("openssl",["version"]),
case re:split(VersionStr, "fips") of
@@ -2910,10 +3034,10 @@ supports_ssl_tls_version(Version) when Version == sslv2;
case ubuntu_legacy_support() of
true ->
case portable_cmd("openssl", ["version"]) of
- "OpenSSL 1.1" ++ _ ->
- false;
- "OpenSSL 1" ++ _ ->
+ "OpenSSL 1.0.1" ++ _ ->
Version =/= sslv2;
+ "OpenSSL 1" ++ _ ->
+ false;
%% Appears to be broken
"OpenSSL 0.9.8.o" ++ _ ->
false;
@@ -3149,6 +3273,41 @@ hardcode_rsa_key(6) ->
coefficient = 81173034184183681160439870161505779100040258708276674532866007896310418779840630960490793104541748007902477778658270784073595697910785917474138815202903114440800310078464142273778315781957021015333260021813037604142367434117205299831740956310682461174553260184078272196958146289378701001596552915990080834227,
otherPrimeInfos = asn1_NOVALUE}.
+hardcode_rsa_1024_key(1) ->
+ #'RSAPrivateKey'{version = 'two-prime',
+ modulus = 152618920709346576506952607098028299458615405194120516804067302859774798720862572082626851690572130284910454988859007980367926204341637028795420927026111160369130942718840998292351565050537705794496742217762844103737634290634532232714374862322877076125650783658974242305324207239909234718160759907957502819181,
+ publicExponent = 17,
+ privateExponent = 89775835711380339121736827704722529093303179525953245178863119329279293365213277695662853935630664873476738228740592929628191884906845311056129957074183020957315463095429495020547731127789657232144654051871515007759243605000909583210051114028049068215595185959728886310943042856399988846590947179831354428913,
+ prime1 = 13018105310773689694711101111767176661493882304979760063552973933059514785910240943852845097923711145970844208861778343060919395218474310542285865516544653,
+ prime2 = 11723589344682921162046319310366118627005968525349821205037572987102618200031016344115630095736447992996683226273798377973464634035204645607416378683745377,
+ exponent1 = 7657709006337464526300647712804221565584636649988094155031161137093832227006024084619320645837477144688731887565751966506423173657926065024874038539143913,
+ exponent2 = 11033966442054514034867124056815170472476205670917478781211833399625993600029191853285298913634303993408643036492986708680907890856663195865803650525878001,
+ coefficient = 7357357483264399363785138527396251818499941660605442417644885251395376792981387533016821796011101917057636813775613592220898054882923958484000235934554630,
+ otherPrimeInfos = asn1_NOVALUE};
+hardcode_rsa_1024_key(2) ->
+ #'RSAPrivateKey'{version = 'two-prime',
+ modulus = 132417984874904377472239436563253515498607309816574784497785056464473431603604973287322746340055062696030016903830406088140534281534301418467490242269156926775506208514027213826501153438861284871625076651352798208559277520683414148048437439635357639033850360133068980157555507518934285770383924814915583919331,
+ publicExponent = 17,
+ privateExponent = 116839398419033274240211267555811925439947626308742456909810343939241263179651447018225952652989761202379426679850358313065177307236148310412491390237491385620149263549211570156731410125598364338974865883306073709062002620705336269289633237348474049621806833904576124689232282666798030505410189805859996211233,
+ prime1 = 12354286715326546399270830019697416039683060665495490476376955068446562229853736822465010796530936501225722243114286822522048306078247961653481711526701259,
+ prime2 = 10718383661165041035044708868932433765604392896488115438294667272770655136522450030638962957185192634722652306257889603065114923772949624056219896061512009,
+ exponent1 = 5087059235722695576170341772816583075163613215204025490272863851713290329939773985720886798571562088740003276576471044567902243679278572445551292981582871,
+ exponent2 = 6304931565391200608849828746430843391531995821463597316643921925159208903836735312140566445403054491013324886034052707685361719866440955327188174153830593,
+ coefficient = 6764088858264512915296172980190092445938774052616013205418164952211827027745702759906572599388571087295432259160097016323193144471211837074613329649320009,
+ otherPrimeInfos = asn1_NOVALUE};
+hardcode_rsa_1024_key(3) ->
+ #'RSAPrivateKey'{version = 'two-prime',
+ modulus = 132603582566987335579015215397416921308461253540735107996254563101087328483405996961761145905021132317760270172654141110354018131670337351296871719192630978670273323069438897632586026697023844069174787494970866246368200405578784055149230641370998125414763230872277095376893138420738507940599560410343688278361,
+ publicExponent = 17,
+ privateExponent = 124803371827752786427308438021098278878551768038338925172945471153964544454970350081657549087078712769656724868380368103862605300395611624749996912181299722918452562565562892031863847812293655197586374503957768862684015202213024002730410420619423541154205461118764880018581745374583581669240937327152309672753,
+ prime1 = 12202483472094988277172439292742673588688995751099198683383744575043357099902468606144011463115716181768777309695574163698153032647393450605174909802187971,
+ prime2 = 10866934003248540047676291395653788246732743513485317053446021859209870346149779563425451397497222238159656279714782986335807210805023580459325334557063091,
+ exponent1 = 3588965727086761257991893909630198114320292867970352553936395463248046205853667237101179842092857700520228620498698283440633244896292191354463208765349403,
+ exponent2 = 4474619883690575313749061162916265748654659093788071727889538412615828966061673937881068222498856215712799644588440053197097086802068533130310431876437743,
+ coefficient = 6440880395775803235356940314241907933534073137546236980469653455119937607298142560546736915150573386382326185901566797818281064505978928392351326571984856,
+ otherPrimeInfos = asn1_NOVALUE}.
+
+
hardcode_dsa_key(1) ->
{'DSAPrivateKey',0,
99438313664986922963487511141216248076486724382260996073922424025828494981416579966171753999204426907349400798052572573634137057487829150578821328280864500098312146772602202702021153757550650696224643730869835650674962433068943942837519621267815961566259265204876799778977478160416743037274938277357237615491,
@@ -3432,3 +3591,31 @@ set_protocol_versions(_, undefined) ->
ok;
set_protocol_versions(AppVar, Value) ->
application:set_env(ssl, AppVar, Value).
+
+openssl_sigalgs(rsa_pss_pss, Config) ->
+ [{openssl_sigalgs, "rsa_pss_rsae_sha256:rsa_pss_pss_sha256"} |
+ proplists:delete(openssl_sigalgs, Config)];
+openssl_sigalgs(_, Config) ->
+ proplists:delete(openssl_sigalgs, Config).
+
+pss_params(sha256) ->
+ #'RSASSA-PSS-params'{
+ hashAlgorithm = #'HashAlgorithm'{algorithm = ?'id-sha256'},
+ maskGenAlgorithm = #'MaskGenAlgorithm'{algorithm = ?'id-mgf1',
+ parameters = #'HashAlgorithm'{algorithm = ?'id-sha256'}
+ },
+ saltLength = 32,
+ trailerField = 1}.
+
+test_ciphers(Kex, Cipher, Version) ->
+ ssl:filter_cipher_suites(
+ ssl:cipher_suites(all, Version) ++ ssl:cipher_suites(anonymous, Version),
+ [{key_exchange,
+ fun(K) when K == Kex -> true;
+ (_) -> false
+ end},
+ {cipher,
+ fun(C) when C == Cipher -> true;
+ (_) -> false
+ end}]).
+
diff --git a/lib/ssl/test/tls_1_3_record_SUITE.erl b/lib/ssl/test/tls_1_3_record_SUITE.erl
index fc7572cdfa..8bf1097e55 100644
--- a/lib/ssl/test/tls_1_3_record_SUITE.erl
+++ b/lib/ssl/test/tls_1_3_record_SUITE.erl
@@ -112,7 +112,7 @@ encode_decode(_Config) ->
<<92,24,205,75,244,60,136,212,250,32,214,20,37,3,213,87,61,207,
147,61,168,145,177,118,160,153,33,53,48,108,191,174>>,
undefined},
- sequence_number => 0,server_verify_data => undefined}},
+ sequence_number => 0,server_verify_data => undefined},max_fragment_length => undefined},
PlainText = [11,
<<0,2,175>>,
@@ -168,7 +168,8 @@ encode_decode(_Config) ->
#{current_write =>
#{security_parameters =>
#security_parameters{cipher_suite = ?TLS_NULL_WITH_NULL_NULL},
- sequence_number => 0
+ sequence_number => 0,
+ max_fragment_length => undefined
}
},
diff --git a/lib/ssl/test/tls_api_SUITE.erl b/lib/ssl/test/tls_api_SUITE.erl
index 01de312a81..6804b09687 100644
--- a/lib/ssl/test/tls_api_SUITE.erl
+++ b/lib/ssl/test/tls_api_SUITE.erl
@@ -29,6 +29,7 @@
-include_lib("ssl/src/ssl_api.hrl").
-include_lib("ssl/src/tls_handshake.hrl").
+-define(TIMEOUT, {seconds, 10}).
-define(SLEEP, 500).
%%--------------------------------------------------------------------
@@ -115,7 +116,7 @@ end_per_group(GroupName, Config) ->
init_per_testcase(_TestCase, Config) ->
ssl_test_lib:ct_log_supported_protocol_versions(Config),
- ct:timetrap({seconds, 10}),
+ ct:timetrap(?TIMEOUT),
Config.
end_per_testcase(_TestCase, Config) ->
diff --git a/lib/ssl/test/x509_test.erl b/lib/ssl/test/x509_test.erl
index dd774d4c7c..e99c35e249 100644
--- a/lib/ssl/test/x509_test.erl
+++ b/lib/ssl/test/x509_test.erl
@@ -64,6 +64,8 @@ do_gen_pem_config_files(Config, CertFile, KeyFile, CAFile) ->
cert_entry(Cert) ->
{'Certificate', Cert, not_encrypted}.
+key_entry({'PrivateKeyInfo', DERKeyInfo}) ->
+ {'PrivateKeyInfo', DERKeyInfo, not_encrypted};
key_entry({'RSAPrivateKey', DERKey}) ->
{'RSAPrivateKey', DERKey, not_encrypted};
key_entry({'DSAPrivateKey', DERKey}) ->
diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk
index c85d1fa173..14ba8ed31c 100644
--- a/lib/ssl/vsn.mk
+++ b/lib/ssl/vsn.mk
@@ -1 +1 @@
-SSL_VSN = 9.6.1
+SSL_VSN = 10.0
diff --git a/lib/stdlib/doc/src/c.xml b/lib/stdlib/doc/src/c.xml
index 50e1dea52b..b481596379 100644
--- a/lib/stdlib/doc/src/c.xml
+++ b/lib/stdlib/doc/src/c.xml
@@ -120,7 +120,7 @@
</func>
<func>
- <name name="h" arity="1" since="OTP @OTP-16222@"/>
+ <name name="h" arity="1" since="OTP 23.0"/>
<fsummary>Module help information</fsummary>
<type name="h_return"/>
<desc>
@@ -129,7 +129,7 @@
</func>
<func>
- <name name="h" arity="2" since="OTP @OTP-16222@"/>
+ <name name="h" arity="2" since="OTP 23.0"/>
<fsummary>Function help information</fsummary>
<type name="h_return"/>
<type name="hf_return"/>
@@ -139,7 +139,7 @@
</func>
<func>
- <name name="h" arity="3" since="OTP @OTP-16222@"/>
+ <name name="h" arity="3" since="OTP 23.0"/>
<fsummary>Function help information</fsummary>
<type name="h_return"/>
<type name="hf_return"/>
@@ -149,7 +149,7 @@
</func>
<func>
- <name name="ht" arity="1" since="OTP @OTP-16222@"/>
+ <name name="ht" arity="1" since="OTP 23.0"/>
<fsummary>Type help information</fsummary>
<type name="h_return"/>
<desc>
@@ -158,7 +158,7 @@
</func>
<func>
- <name name="ht" arity="2" since="OTP @OTP-16222@"/>
+ <name name="ht" arity="2" since="OTP 23.0"/>
<fsummary>Type help information</fsummary>
<type name="h_return"/>
<type name="ht_return"/>
@@ -168,7 +168,7 @@
</func>
<func>
- <name name="ht" arity="3" since="OTP @OTP-16222@"/>
+ <name name="ht" arity="3" since="OTP 23.0"/>
<fsummary>Type help information</fsummary>
<type name="h_return"/>
<type name="ht_return"/>
diff --git a/lib/stdlib/doc/src/gen_event.xml b/lib/stdlib/doc/src/gen_event.xml
index 3a5e4cb619..2ddff240d2 100644
--- a/lib/stdlib/doc/src/gen_event.xml
+++ b/lib/stdlib/doc/src/gen_event.xml
@@ -524,9 +524,9 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
- <name since="OTP @OTP-16120@">start_monitor() -> Result</name>
- <name since="OTP @OTP-16120@">start_monitor(EventMgrName | Options) -> Result</name>
- <name since="OTP @OTP-16120@">start_monitor(EventMgrName, Options) -> Result</name>
+ <name since="OTP 23.0">start_monitor() -> Result</name>
+ <name since="OTP 23.0">start_monitor(EventMgrName | Options) -> Result</name>
+ <name since="OTP 23.0">start_monitor(EventMgrName, Options) -> Result</name>
<fsummary>Create a stand-alone event manager process.</fsummary>
<type>
<v>EventMgrName = {local,Name} | {global,GlobalName} | {via,Module,ViaName}</v>
diff --git a/lib/stdlib/doc/src/gen_server.xml b/lib/stdlib/doc/src/gen_server.xml
index 4ab8360fd1..4abb91439e 100644
--- a/lib/stdlib/doc/src/gen_server.xml
+++ b/lib/stdlib/doc/src/gen_server.xml
@@ -551,8 +551,8 @@ gen_server:abcast -----> Module:handle_cast/2
</func>
<func>
- <name since="OTP @OTP-16120@">start_monitor(Module, Args, Options) -> Result</name>
- <name since="OTP @OTP-16120@">start_monitor(ServerName, Module, Args, Options) -> Result</name>
+ <name since="OTP 23.0">start_monitor(Module, Args, Options) -> Result</name>
+ <name since="OTP 23.0">start_monitor(ServerName, Module, Args, Options) -> Result</name>
<fsummary>Create a standalone <c>gen_server</c> process.</fsummary>
<type>
<v>ServerName = {local,Name} | {global,GlobalName}</v>
diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml
index 1674773c64..fa3f20535d 100644
--- a/lib/stdlib/doc/src/gen_statem.xml
+++ b/lib/stdlib/doc/src/gen_statem.xml
@@ -1250,7 +1250,7 @@ handle_event(_, _, State, Data) ->
</p>
</item>
<tag>
- <c>push_callback_module</c><br />
+ <c>push_callback_module</c>
</tag>
<item>
<p>
@@ -2049,8 +2049,8 @@ handle_event(_, _, State, Data) ->
</func>
<func>
- <name name="start_monitor" arity="3" since="OTP @OTP-16120@"/>
- <name name="start_monitor" arity="4" since="OTP @OTP-16120@"/>
+ <name name="start_monitor" arity="3" since="OTP 23.0"/>
+ <name name="start_monitor" arity="4" since="OTP 23.0"/>
<fsummary>Create a standalone <c>gen_statem</c> process.</fsummary>
<desc>
<p>
diff --git a/lib/stdlib/doc/src/notes.xml b/lib/stdlib/doc/src/notes.xml
index f1737f071b..24a8d4c089 100644
--- a/lib/stdlib/doc/src/notes.xml
+++ b/lib/stdlib/doc/src/notes.xml
@@ -31,6 +31,209 @@
</header>
<p>This document describes the changes made to the STDLIB application.</p>
+<section><title>STDLIB 3.13</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Compiling a match specification with excessive nesting
+ caused the runtime system to crash due to scheduler stack
+ exhaustion. Instead of crashing the runtime system,
+ effected functions will now raise a <c>system_limit</c>
+ error exception in this situation.</p>
+ <p>
+ Own Id: OTP-16431 Aux Id: ERL-592 </p>
+ </item>
+ <item>
+ <p> Initialization of record fields using <c>_</c> is no
+ longer allowed if the number of affected fields is zero.
+ </p>
+ <p>
+ Own Id: OTP-16516</p>
+ </item>
+ <item>
+ <p> Fix bugs in <c>eval_bits</c>. </p>
+ <p>
+ Own Id: OTP-16545</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Improved the printout of single line logger events for
+ most of the OTP behaviours in STDLIB and Kernel. This
+ includes <c>proc_lib</c>, <c>gen_server</c>,
+ <c>gen_event</c>, <c>gen_statem</c>, <c>gen_fsm</c>,
+ <c>supervisor</c>, <c>supervisor_bridge</c> and
+ <c>application</c>.</p>
+ <p>
+ Improved the <seeerl
+ marker="kernel:logger_formatter#chars_limit"><c>chars_limit</c></seeerl>
+ and <seeerl
+ marker="kernel:logger_formatter#depth"><c>depth</c></seeerl>
+ handling in <c>proc_lib</c> and when formatting of
+ exceptions.</p>
+ <p>
+ Own Id: OTP-15299</p>
+ </item>
+ <item>
+ <p>
+ Remove usage and documentation of old requests of the
+ I/O-protocol.</p>
+ <p>
+ Own Id: OTP-15695</p>
+ </item>
+ <item>
+ <p>Improved ETS scalability of concurrent calls that
+ change the size of a table, like <c>ets:insert/2</c> and
+ <c>ets:delete/2</c>.</p> <p>This performance feature was
+ implemented for <c>ordered_set</c> in OTP 22.0 and does
+ now apply for all ETS table types.</p> <p>The improved
+ scalability may come at the cost of longer latency of
+ <c>ets:info(T,size)</c> and <c>ets:info(T,memory)</c>. A
+ new table option <c>decentralized_counters</c> has
+ therefore been added. It is default <c>true</c> for
+ <c>ordered_set</c> with <c>write_concurrency</c> enabled
+ and default <c>false</c> for all other table types.</p>
+ <p>
+ Own Id: OTP-15744 Aux Id: OTP-15623, PR-2229 </p>
+ </item>
+ <item>
+ <p> Handle Unicode filenames in the <c>zip</c> module.
+ </p>
+ <p>
+ Own Id: OTP-16005 Aux Id: ERL-1003, ERL-1150 </p>
+ </item>
+ <item>
+ <p>
+ Unicode support was updated to the Unicode 12.1 standard.</p>
+ <p>
+ Own Id: OTP-16073 Aux Id: PR-2339 </p>
+ </item>
+ <item>
+ <p>
+ All of the modules <seemfa
+ marker="stdlib:proc_lib#start_monitor/3"><c>proc_lib</c></seemfa>,
+ <seemfa
+ marker="stdlib:gen_server#start_monitor/3"><c>gen_server</c></seemfa>,
+ <seemfa
+ marker="stdlib:gen_statem#start_monitor/3"><c>gen_statem</c></seemfa>,
+ and <seemfa
+ marker="stdlib:gen_event#start_monitor/0"><c>gen_event</c></seemfa>
+ have been extended with a <c>start_monitor()</c>
+ function. For more information, see the documentation of
+ <c>start_monitor()</c> for these modules.</p>
+ <p>
+ Own Id: OTP-16120 Aux Id: ERIERL-402, PR-2427 </p>
+ </item>
+ <item>
+ <p>
+ Updates for new <c>erlang:term_to_iovec()</c> BIF.</p>
+ <p>
+ Own Id: OTP-16128 Aux Id: OTP-15618 </p>
+ </item>
+ <item>
+ <p>Documented a quirk regarding extraction from file
+ descriptors in <c>erl_tar</c>.</p>
+ <p>
+ Own Id: OTP-16171 Aux Id: ERL-1057 </p>
+ </item>
+ <item>
+ <p>
+ Added <c>ok</c> as return value to
+ <c>gen_server:reply/2</c></p>
+ <p>
+ Own Id: OTP-16210 Aux Id: PR-2411 </p>
+ </item>
+ <item>
+ <p>New functions have been added to <seeerl
+ marker="c"><c>c(3)</c></seeerl> for printing embedded
+ documentation for Erlang modules. The functions are:</p>
+ <taglist> <tag>h/1,2,3</tag> <item>Print the
+ documentation for a Module:Function/Arity.</item>
+ <tag>ht/1,2,3</tag> <item>Print the type documentation
+ for a Module:Type/Arity.</item> </taglist> <p>The
+ embedded documentation is created when building the
+ Erlang/OTP documentation.</p>
+ <p>
+ Own Id: OTP-16222</p>
+ </item>
+ <item>
+ <p> Add <c>indent</c> and <c>linewidth</c> to the options
+ of the <c>erl_pp</c> module's functions. </p>
+ <p>
+ Own Id: OTP-16276 Aux Id: PR-2443 </p>
+ </item>
+ <item>
+ <p>
+ Minor updates due to the new spawn improvements made.</p>
+ <p>
+ Own Id: OTP-16368 Aux Id: OTP-15251 </p>
+ </item>
+ <item>
+ <p>The compiler will now raise a warning when inlining is
+ used in modules that load NIFs.</p>
+ <p>
+ Own Id: OTP-16429 Aux Id: ERL-303 </p>
+ </item>
+ <item>
+ <p>Refactored the internal handling of deprecated and
+ removed functions.</p>
+ <p>
+ Own Id: OTP-16469</p>
+ </item>
+ <item>
+ <p> Extend <c>erl_parse:abstract/1,2</c> to handle
+ external fun expressions (<c>fun M:F/A</c>). </p>
+ <p>
+ Own Id: OTP-16480</p>
+ </item>
+ <item>
+ <p>Added <c>filelib:safe_relative_path/2</c> to replace
+ <c>filename:safe_relative_path/1</c>, which did not
+ safely handle symbolic links.</p>
+ <p><c>filename:safe_relative_path/1</c> has been
+ deprecated.</p>
+ <p>
+ Own Id: OTP-16483 Aux Id: PR-2542 </p>
+ </item>
+ <item>
+ <p>
+ The module <c>shell_docs</c> has been added. The module
+ contains functions for rendering, validating and
+ normalizing embedded documentation.</p>
+ <p>
+ Own Id: OTP-16500</p>
+ </item>
+ <item>
+ <p>
+ Module and function auto-completion in the shell now
+ looks at all available modules instead of only those
+ loaded. A module is considered available if it either is
+ loaded already or would be loaded if called.</p>
+ <p>
+ The auto-completion has also been expanded to work in the
+ new <c>h/1,2,3</c> function in <c>c(3)</c>.</p>
+ <p>
+ Own Id: OTP-16501 Aux Id: OTP-16494, OTP-16222,
+ OTP-16406, OTP-16499, OTP-16500, PR-2545, ERL-708 </p>
+ </item>
+ <item>
+ <p>Updated the internal <c>pcre</c> library to
+ <c>8.44</c>.</p>
+ <p>
+ Own Id: OTP-16557</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>STDLIB 3.12.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -653,6 +856,27 @@
</section>
+<section><title>STDLIB 3.8.2.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ <seemfa marker="stdlib:re#run/3">re:run(Subject, RE,
+ [unicode])</seemfa> returned <c>nomatch</c> instead of
+ failing with a <c>badarg</c> error exception when
+ <c>Subject</c> contained illegal utf8 and <c>RE</c> was
+ passed as a binary. This has been corrected along with
+ corrections of reduction counting in <c>re:run()</c>
+ error cases.</p>
+ <p>
+ Own Id: OTP-16553</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>STDLIB 3.8.2.3</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/stdlib/doc/src/proc_lib.xml b/lib/stdlib/doc/src/proc_lib.xml
index 709f157b14..aa649a280a 100644
--- a/lib/stdlib/doc/src/proc_lib.xml
+++ b/lib/stdlib/doc/src/proc_lib.xml
@@ -360,9 +360,9 @@ init(Parent) ->
</func>
<func>
- <name name="start_monitor" arity="3" since="OTP @OTP-16120@"/>
- <name name="start_monitor" arity="4" since="OTP @OTP-16120@"/>
- <name name="start_monitor" arity="5" since="OTP @OTP-16120@"/>
+ <name name="start_monitor" arity="3" since="OTP 23.0"/>
+ <name name="start_monitor" arity="4" since="OTP 23.0"/>
+ <name name="start_monitor" arity="5" since="OTP 23.0"/>
<fsummary>Start a new process synchronously.</fsummary>
<desc>
<p>
diff --git a/lib/stdlib/doc/src/shell.xml b/lib/stdlib/doc/src/shell.xml
index b601c2397d..62ce63ec33 100644
--- a/lib/stdlib/doc/src/shell.xml
+++ b/lib/stdlib/doc/src/shell.xml
@@ -119,6 +119,9 @@
<section>
<title>Shell Commands</title>
+ <p>The commands below are the built-in shell commands that are always
+ available. In most system the commands listed in the <seeerl marker="c">c(3)</seeerl>
+ module are also available in the shell.</p>
<taglist>
<tag><c>b()</c></tag>
<item>
@@ -740,8 +743,8 @@ q - quit erlang
<p>If you want an Erlang node to have a remote job active from the start
(rather than the default local job), start Erlang with flag
- <c>-remsh</c>, for example,
- <c>erl -sname this_node -remsh other_node@other_host</c></p>
+ <seecom marker="erts:erl#remsh"><c>-remsh</c></seecom>, for example,
+ <c>erl -remsh other_node@other_host</c></p>
</section>
<section>
diff --git a/lib/stdlib/doc/src/shell_docs.xml b/lib/stdlib/doc/src/shell_docs.xml
index 4a39ef9eef..c8fba1b43e 100644
--- a/lib/stdlib/doc/src/shell_docs.xml
+++ b/lib/stdlib/doc/src/shell_docs.xml
@@ -32,11 +32,11 @@
<rev>A</rev>
<file>shell_docs.xml</file>
</header>
- <module since="OTP @OTP-16222@">shell_docs</module>
- <modulesummary>Functions used to render documentation for a shell.</modulesummary>
+ <module since="OTP 23.0">shell_docs</module>
+ <modulesummary>Functions used to render EEP-48 style documentation for a shell.</modulesummary>
<description>
<p>This module can be used to render function and type documentation
- to be printed in a shell. It can only render documentation of the format
+ to be printed in a shell. It can only render EEP-48 documentation of the format
<c>application/erlang+html</c>. For more information about this format see
<seeguide marker="erl_docgen:doc_storage">Documentation Storage</seeguide>
in Erl_Docgen's User's Guide.
@@ -48,6 +48,8 @@
<name name="docs_v1"/>
</datatype>
<datatype>
+ <name name="chunk_element_block_type"/>
+ <name name="chunk_element_inline_type"/>
<name name="chunk_element_type"/>
<desc>
<p>
@@ -68,38 +70,53 @@
<funcs>
<func>
- <name name="render" arity="2" since="OTP @OTP-16222@"/>
+ <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 @OTP-16222@"/>
- <name name="render" arity="4" since="OTP @OTP-16222@"/>
+ <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>
<desc>
<p>Render the documentation for a function.</p>
</desc>
</func>
<func>
- <name name="render_type" arity="2" since="OTP @OTP-16222@"/>
+ <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 @OTP-16222@"/>
- <name name="render_type" arity="4" since="OTP @OTP-16222@"/>
+ <name name="render_type" arity="3" since="OTP 23.0"/>
+ <name name="render_type" arity="4" since="OTP 23.0"/>
<fsummary>Render the documentation of a type in a module.</fsummary>
<desc>
<p>Render the documentation of a type in a module.</p>
</desc>
</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"/>
+ <fsummary>Render the documentation of a callback in a module.</fsummary>
+ <desc>
+ <p>Render the documentation of a callback in a module.</p>
+ </desc>
+ </func>
<func>
- <name name="validate" arity="1" since="OTP @OTP-16222@"/>
+ <name name="validate" arity="1" since="OTP 23.0"/>
<fsummary>Validate the documentation</fsummary>
<desc>
<p>This function can be used to do a basic validation of
@@ -108,7 +125,7 @@
</func>
<func>
- <name name="normalize" arity="1" since="OTP @OTP-16222@"/>
+ <name name="normalize" arity="1" since="OTP 23.0"/>
<fsummary>Normalize the documentation</fsummary>
<desc>
<p>This function can be used to do whitespace normalization
diff --git a/lib/stdlib/examples/erl_id_trans.erl b/lib/stdlib/examples/erl_id_trans.erl
index eab2ec4164..a707c45eb9 100644
--- a/lib/stdlib/examples/erl_id_trans.erl
+++ b/lib/stdlib/examples/erl_id_trans.erl
@@ -193,9 +193,6 @@ pattern({map_field_exact,Line,K,V}) ->
Ke = expr(K),
Ve = pattern(V),
{map_field_exact,Line,Ke,Ve};
-%%pattern({struct,Line,Tag,Ps0}) ->
-%% Ps1 = pattern_list(Ps0),
-%% {struct,Line,Tag,Ps1};
pattern({record,Line,Name,Pfs0}) ->
Pfs1 = pattern_fields(Pfs0),
{record,Line,Name,Pfs1};
@@ -433,9 +430,6 @@ expr({map_field_exact,Line,K,V}) ->
Ke = expr(K),
Ve = expr(V),
{map_field_exact,Line,Ke,Ve};
-%%expr({struct,Line,Tag,Es0}) ->
-%% Es1 = pattern_list(Es0),
-%% {struct,Line,Tag,Es1};
expr({record_index,Line,Name,Field0}) ->
Field1 = expr(Field0),
{record_index,Line,Name,Field1};
diff --git a/lib/stdlib/src/c.erl b/lib/stdlib/src/c.erl
index 6af3951604..fa7bf8bfbc 100644
--- a/lib/stdlib/src/c.erl
+++ b/lib/stdlib/src/c.erl
@@ -30,7 +30,7 @@
lc_batch/0, lc_batch/1,
i/3,pid/3,m/0,m/1,mm/0,lm/0,
bt/1, q/0,
- h/1,h/2,h/3,ht/1,ht/2,ht/3,
+ h/1,h/2,h/3,ht/1,ht/2,ht/3,hcb/1,hcb/2,hcb/3,
erlangrc/0,erlangrc/1,bi/1, flush/0, regs/0, uptime/0,
nregs/0,pwd/0,ls/0,ls/1,cd/1,memory/1,memory/0, xm/1]).
@@ -154,8 +154,9 @@ c(SrcFile, NewOpts, Filter, BeamFile, Info) ->
safe_recompile(SrcFile, Options, BeamFile).
-type h_return() :: ok | {error, missing | {unknown_format, unicode:chardata()}}.
--type ht_return() :: h_return() | {error, type_missing}.
-type hf_return() :: h_return() | {error, function_missing}.
+-type ht_return() :: h_return() | {error, type_missing}.
+-type hcb_return() :: h_return() | {error, callback_missing}.
-spec h(module()) -> h_return().
h(Module) ->
@@ -224,10 +225,49 @@ ht(Module,Type,Arity) ->
Error
end.
+-spec hcb(module()) -> h_return().
+hcb(Module) ->
+ case code:get_doc(Module) of
+ {ok, #docs_v1{ format = ?NATIVE_FORMAT } = Docs} ->
+ format_docs(shell_docs:render_callback(Module, Docs));
+ {ok, #docs_v1{ format = Enc }} ->
+ {error, {unknown_format, Enc}};
+ Error ->
+ Error
+ end.
+
+-spec hcb(module(),Callback :: atom()) -> hcb_return().
+hcb(Module,Callback) ->
+ case code:get_doc(Module) of
+ {ok, #docs_v1{ format = ?NATIVE_FORMAT } = Docs} ->
+ format_docs(shell_docs:render_callback(Module, Callback, Docs));
+ {ok, #docs_v1{ format = Enc }} ->
+ {error, {unknown_format, Enc}};
+ Error ->
+ Error
+ end.
+
+-spec hcb(module(),Callback :: atom(),arity()) ->
+ hcb_return().
+hcb(Module,Callback,Arity) ->
+ case code:get_doc(Module) of
+ {ok, #docs_v1{ format = ?NATIVE_FORMAT } = Docs} ->
+ format_docs(shell_docs:render_callback(Module, Callback, Arity, Docs));
+ {ok, #docs_v1{ format = Enc }} ->
+ {error, {unknown_format, Enc}};
+ Error ->
+ Error
+ end.
+
format_docs({error,_} = E) ->
E;
format_docs(Docs) ->
- format("~ts",[Docs]).
+ {match, Lines} = re:run(Docs,"(.+\n|\n)",[unicode,global,{capture,all_but_first,binary}]),
+ _ = paged_output(fun(Line,_) ->
+ format("~ts",Line),
+ {1,undefined}
+ end, undefined, Lines),
+ ok.
old_options(Info) ->
case lists:keyfind(options, 1, Info) of
@@ -557,58 +597,54 @@ ni() -> i(all_procs()).
-spec i([pid()]) -> 'ok'.
i(Ps) ->
- i(Ps, length(Ps)).
-
--spec i([pid()], non_neg_integer()) -> 'ok'.
-
-i(Ps, N) when N =< 100 ->
- iformat("Pid", "Initial Call", "Heap", "Reds",
- "Msgs"),
- iformat("Registered", "Current Function", "Stack", "",
- ""),
- {R,M,H,S} = foldl(fun(Pid, {R0,M0,H0,S0}) ->
- {A,B,C,D} = display_info(Pid),
- {R0+A,M0+B,H0+C,S0+D}
- end, {0,0,0,0}, Ps),
- iformat("Total", "", w(H), w(R), w(M)),
- iformat("", "", w(S), "", "");
-i(Ps, N) ->
- iformat("Pid", "Initial Call", "Heap", "Reds",
- "Msgs"),
- iformat("Registered", "Current Function", "Stack", "",
- ""),
- paged_i(Ps, {0,0,0,0}, N, 50).
-
-paged_i([], {R,M,H,S}, _, _) ->
- iformat("Total", "", w(H), w(R), w(M)),
- iformat("", "", w(S), "", "");
-paged_i(Ps, Acc, N, Page) ->
- {Pids, Rest, N1} =
- if N > Page ->
- {L1,L2} = lists:split(Page, Ps),
- {L1,L2,N-Page};
- true ->
- {Ps, [], 0}
- end,
- NewAcc = foldl(fun(Pid, {R,M,H,S}) ->
- {A,B,C,D} = display_info(Pid),
- {R+A,M+B,H+C,S+D}
- end, Acc, Pids),
- case Rest of
- [_|_] ->
- choice(fun() -> paged_i(Rest, NewAcc, N1, Page) end);
- [] ->
- paged_i([], NewAcc, 0, Page)
+ iformat("Pid", "Initial Call", "Heap", "Reds", "Msgs"),
+ iformat("Registered", "Current Function", "Stack", "", ""),
+ case paged_output(fun(Pid, {R,M,H,S}) ->
+ {A,B,C,D} = display_info(Pid),
+ {2,{R+A,M+B,H+C,S+D}}
+ end, 2, {0,0,0,0}, Ps) of
+ {R,M,H,S} ->
+ iformat("Total", "", w(H), w(R), w(M)),
+ iformat("", "", w(S), "", "");
+ less ->
+ ok
end.
-choice(F) ->
- case get_line('(c)ontinue (q)uit -->', "c\n") of
+paged_output(Fun, Acc, Items) ->
+ paged_output(Fun, 0, Acc, Items).
+paged_output(Fun, CurrLine, Acc, Items) ->
+ Limit =
+ case io:rows() of
+ {ok, Rows} -> Rows-2;
+ _ -> 100
+ end,
+ paged_output(Fun, CurrLine, Limit, Acc, Items).
+
+paged_output(PrintFun, CurrLine, Limit, Acc, Items) when CurrLine >= Limit ->
+ case more() of
+ more ->
+ paged_output(PrintFun, 0, Limit, Acc, Items);
+ less ->
+ less
+ end;
+paged_output(PrintFun, CurrLine, Limit, Acc, [H|T]) ->
+ {Lines, NewAcc} = PrintFun(H, Acc),
+ paged_output(PrintFun, CurrLine+Lines, Limit, NewAcc, T);
+paged_output(_, _, _, Acc, []) ->
+ Acc.
+
+more() ->
+ case get_line('more (y/n)? (y) ', "y\n") of
"c\n" ->
- F();
+ more;
+ "y\n" ->
+ more;
"q\n" ->
- quit;
+ less;
+ "n\n" ->
+ less;
_ ->
- choice(F)
+ more()
end.
get_line(P, Default) ->
diff --git a/lib/stdlib/src/digraph.erl b/lib/stdlib/src/digraph.erl
index 8a4df95027..58d493cf54 100644
--- a/lib/stdlib/src/digraph.erl
+++ b/lib/stdlib/src/digraph.erl
@@ -230,7 +230,7 @@ in_neighbours(G, V) ->
Edges :: [edge()].
in_edges(G, V) ->
- ets:select(G#digraph.ntab, [{{{in, V}, '$1'}, [], ['$1']}]).
+ [E || {{in, _}, E} <- ets:lookup(G#digraph.ntab, {in, V})].
-spec out_degree(G, V) -> non_neg_integer() when
G :: graph(),
@@ -255,7 +255,7 @@ out_neighbours(G, V) ->
Edges :: [edge()].
out_edges(G, V) ->
- ets:select(G#digraph.ntab, [{{{out, V}, '$1'}, [], ['$1']}]).
+ [E || {{out, _}, E} <- ets:lookup(G#digraph.ntab, {out, V})].
-spec add_edge(G, V1, V2) -> edge() | {'error', add_edge_err_rsn()} when
G :: graph(),
diff --git a/lib/stdlib/src/erl_expand_records.erl b/lib/stdlib/src/erl_expand_records.erl
index 5351490b1a..6200978d4d 100644
--- a/lib/stdlib/src/erl_expand_records.erl
+++ b/lib/stdlib/src/erl_expand_records.erl
@@ -20,10 +20,6 @@
%% Purpose: Expand records into tuples. Also add explicit module
%% names to calls to imported functions and BIFs.
-%% N.B. Although structs (tagged tuples) are not yet allowed in the
-%% language there is code included in pattern/2 and expr/3 (commented out)
-%% that handles them.
-
-module(erl_expand_records).
-export([module/2]).
@@ -125,9 +121,6 @@ pattern({map_field_exact,Line,K0,V0}, St0) ->
{K,St1} = expr(K0, St0),
{V,St2} = pattern(V0, St1),
{{map_field_exact,Line,K,V},St2};
-%%pattern({struct,Line,Tag,Ps}, St0) ->
-%% {TPs,TPsvs,St1} = pattern_list(Ps, St0),
-%% {{struct,Line,Tag,TPs},TPsvs,St1};
pattern({record_index,Line,Name,Field}, St) ->
{index_expr(Line, Field, Name, record_fields(Name, St)),St};
pattern({record,Line0,Name,Pfs}, St0) ->
@@ -310,9 +303,6 @@ expr({map_field_exact,Line,K0,V0}, St0) ->
{K,St1} = expr(K0, St0),
{V,St2} = expr(V0, St1),
{{map_field_exact,Line,K,V},St2};
-%%expr({struct,Line,Tag,Es0}, Vs, St0) ->
-%% {Es1,Esvs,Esus,St1} = expr_list(Es0, Vs, St0),
-%% {{struct,Line,Tag,Es1},Esvs,Esus,St1};
expr({record_index,Line,Name,F}, St) ->
I = index_expr(Line, F, Name, record_fields(Name, St)),
expr(I, St);
diff --git a/lib/stdlib/src/erl_internal.erl b/lib/stdlib/src/erl_internal.erl
index f5059ac710..6ff5e23ee3 100644
--- a/lib/stdlib/src/erl_internal.erl
+++ b/lib/stdlib/src/erl_internal.erl
@@ -311,7 +311,6 @@ bif(is_process_alive, 1) -> true;
bif(is_atom, 1) -> true;
bif(is_boolean, 1) -> true;
bif(is_binary, 1) -> true;
-bif(is_bitstr, 1) -> true;
bif(is_bitstring, 1) -> true;
bif(is_float, 1) -> true;
bif(is_function, 1) -> true;
@@ -348,7 +347,6 @@ bif(max,2) -> true;
bif(min,2) -> true;
bif(module_loaded, 1) -> true;
bif(monitor, 2) -> true;
-bif(monitor, 3) -> true;
bif(monitor_node, 2) -> true;
bif(node, 0) -> true;
bif(node, 1) -> true;
@@ -465,7 +463,6 @@ old_bif(is_process_alive, 1) -> true;
old_bif(is_atom, 1) -> true;
old_bif(is_boolean, 1) -> true;
old_bif(is_binary, 1) -> true;
-old_bif(is_bitstr, 1) -> true;
old_bif(is_bitstring, 1) -> true;
old_bif(is_float, 1) -> true;
old_bif(is_function, 1) -> true;
diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl
index 4892dd5131..7c717e47d1 100644
--- a/lib/stdlib/src/erl_lint.erl
+++ b/lib/stdlib/src/erl_lint.erl
@@ -20,9 +20,6 @@
%%
%% Do necessary checking of Erlang code.
-%% N.B. All the code necessary for checking structs (tagged tuples) is
-%% here. Just comment out the lines in pattern/2, gexpr/3 and expr/3.
-
-module(erl_lint).
-export([module/1,module/2,module/3,format_error/1]).
@@ -1646,8 +1643,6 @@ pattern({tuple,_Line,Ps}, Vt, Old, Bvt, St) ->
pattern_list(Ps, Vt, Old, Bvt, St);
pattern({map,_Line,Ps}, Vt, Old, Bvt, St) ->
pattern_map(Ps, Vt, Old, Bvt, St);
-%%pattern({struct,_Line,_Tag,Ps}, Vt, Old, Bvt, St) ->
-%% pattern_list(Ps, Vt, Old, Bvt, St);
pattern({record_index,Line,Name,Field}, _Vt, _Old, _Bvt, St) ->
{Vt1,St1} =
check_record(Line, Name, St,
@@ -2266,8 +2261,6 @@ is_gexpr({string,_L,_S}, _Info) -> true;
is_gexpr({nil,_L}, _Info) -> true;
is_gexpr({cons,_L,H,T}, Info) -> is_gexpr_list([H,T], Info);
is_gexpr({tuple,_L,Es}, Info) -> is_gexpr_list(Es, Info);
-%%is_gexpr({struct,_L,_Tag,Es}, Info) ->
-%% is_gexpr_list(Es, Info);
is_gexpr({map,_L,Es}, Info) ->
is_map_fields(Es, Info);
is_gexpr({map,_L,Src,Es}, Info) ->
diff --git a/lib/stdlib/src/erl_pp.erl b/lib/stdlib/src/erl_pp.erl
index 387fa395ce..651c601bb0 100644
--- a/lib/stdlib/src/erl_pp.erl
+++ b/lib/stdlib/src/erl_pp.erl
@@ -560,8 +560,6 @@ lexpr({bc,_,E,Qs}, _Prec, Opts) ->
%% {list,[{step,'<<',Lcl},'>>']};
lexpr({tuple,_,Elts}, _, Opts) ->
tuple(Elts, Opts);
-%%lexpr({struct,_,Tag,Elts}, _, Opts) ->
-%% {first,format("~w", [Tag]),tuple(Elts, Opts)};
lexpr({record_index, _, Name, F}, Prec, Opts) ->
{P,R} = preop_prec('#'),
Nl = record_name(Name),
diff --git a/lib/stdlib/src/otp_internal.erl b/lib/stdlib/src/otp_internal.erl
index 11594483e0..b6531d9b5c 100644
--- a/lib/stdlib/src/otp_internal.erl
+++ b/lib/stdlib/src/otp_internal.erl
@@ -245,6 +245,10 @@ obsolete(snmpm, sync_set, 5) ->
{deprecated, "use snmpm:sync_set2/4 instead.", "OTP 25"};
obsolete(snmpm, sync_set, 6) ->
{deprecated, "use snmpm:sync_set2/4 instead.", "OTP 25"};
+obsolete(ssl, cipher_suites, 0) ->
+ {deprecated, "use cipher_suites/2,3 instead", "OTP 24"};
+obsolete(ssl, cipher_suites, 1) ->
+ {deprecated, "use cipher_suites/2,3 instead", "OTP 24"};
obsolete(sys, get_debug, 3) ->
{deprecated, "incorrectly documented and only for internal use. Can often be replaced with sys:get_log/1"};
obsolete(wxCalendarCtrl, enableYearChange, 1) ->
@@ -466,7 +470,7 @@ obsolete(crypto, stream_init, _) ->
obsolete(filename, find_src, _) ->
{deprecated, "use filelib:find_source/1,3 instead", "OTP 24"};
obsolete(ssl, ssl_accept, _) ->
- {deprecated, "use ssl_handshake/1,2,3 instead"};
+ {deprecated, "use ssl_handshake/1,2,3 instead", "OTP 24"};
obsolete(asn1ct, decode, _) ->
{removed, "use Mod:decode/2 instead"};
obsolete(asn1ct, encode, _) ->
@@ -494,6 +498,14 @@ obsolete(os_mon_mib, _, _) ->
obsolete(_,_,_) -> no.
-dialyzer({no_match, obsolete_type/3}).
+obsolete_type(crypto, retired_cbc_cipher_aliases, 0) ->
+ {deprecated, "Use aes_*_cbc or des_ede3_cbc"};
+obsolete_type(crypto, retired_cfb_cipher_aliases, 0) ->
+ {deprecated, "Use aes_*_cfb8, aes_*_cfb128 or des_ede3_cfb"};
+obsolete_type(crypto, retired_ctr_cipher_aliases, 0) ->
+ {deprecated, "Use aes_*_ctr"};
+obsolete_type(crypto, retired_ecb_cipher_aliases, 0) ->
+ {deprecated, "Use aes_*_ecb"};
obsolete_type(erl_scan, column, 0) ->
{removed, "use erl_anno:column() instead"};
obsolete_type(erl_scan, line, 0) ->
diff --git a/lib/stdlib/src/otp_internal.hrl b/lib/stdlib/src/otp_internal.hrl
index ace1fa5cc1..17e15da68f 100644
--- a/lib/stdlib/src/otp_internal.hrl
+++ b/lib/stdlib/src/otp_internal.hrl
@@ -26,7 +26,7 @@
-export([obsolete/3, obsolete_type/3]).
-type tag() :: 'deprecated' | 'removed'. %% | 'experimental'.
--type mfas() :: mfa() | {atom(), atom(), [byte()]}.
+-type mfas() :: mfa() | {atom(), atom(), [byte()]} | string().
-type release() :: string().
-spec obsolete(module(), atom(), arity()) ->
diff --git a/lib/stdlib/src/shell_default.erl b/lib/stdlib/src/shell_default.erl
index e65340e663..4819beb62b 100644
--- a/lib/stdlib/src/shell_default.erl
+++ b/lib/stdlib/src/shell_default.erl
@@ -28,7 +28,7 @@
erlangrc/1,bi/1, regs/0, flush/0,pwd/0,ls/0,ls/1,cd/1,
y/1, y/2,
xm/1, bt/1, q/0,
- h/1, h/2, h/3, ht/1, ht/2, ht/3,
+ h/1, h/2, h/3, ht/1, ht/2, ht/3, hcb/1, hcb/2, hcb/3,
ni/0, nregs/0]).
-export([ih/0,iv/0,im/0,ii/1,ii/2,iq/1,ini/1,ini/2,inq/1,ib/2,ib/3,
@@ -49,8 +49,11 @@ help() ->
format("h(Mod,Func)-- help about function in module\n"),
format("h(Mod,Func,Arity) -- help about function with arity in module\n"),
format("ht(Mod) -- help about a module's types\n"),
- format("ht(Mod,Func) -- help about type in module\n"),
- format("ht(Mod,Func,Arity) -- help about type with arity in module\n"),
+ format("ht(Mod,Type) -- help about type in module\n"),
+ format("ht(Mod,Type,Arity) -- help about type with arity in module\n"),
+ format("hcb(Mod) -- help about a module's callbacks\n"),
+ format("hcb(Mod,CB) -- help about callback in module\n"),
+ format("hcb(Mod,CB,Arity) -- help about callback with arity in module\n"),
format("history(N) -- set how many previous commands to keep\n"),
format("results(N) -- set how many previous command results to keep\n"),
format("catch_exception(B) -- how exceptions are handled\n"),
@@ -89,6 +92,9 @@ h(M,F,A) -> c:h(M,F,A).
ht(M) -> c:ht(M).
ht(M,F) -> c:ht(M,F).
ht(M,F,A) -> c:ht(M,F,A).
+hcb(M) -> c:hcb(M).
+hcb(M,F) -> c:hcb(M,F).
+hcb(M,F,A) -> c:hcb(M,F,A).
i() -> c:i().
i(X,Y,Z) -> c:i(X,Y,Z).
l(Mod) -> c:l(Mod).
diff --git a/lib/stdlib/src/shell_docs.erl b/lib/stdlib/src/shell_docs.erl
index ad93268fb2..03e7c47309 100644
--- a/lib/stdlib/src/shell_docs.erl
+++ b/lib/stdlib/src/shell_docs.erl
@@ -23,38 +23,41 @@
-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]).
%% Used by chunks.escript in erl_docgen
-export([validate/1, normalize/1]).
%% Convinience functions
--export([get_doc/1, get_doc/3, get_type_doc/3]).
+-export([get_doc/1, get_doc/3, get_type_doc/3, get_callback_doc/3]).
-record(config, { docs,
io_opts = io:getopts(),
io_columns = element(2,io:columns())
}).
--define(ALL_ELEMENTS,[a,p,'div',h1,h2,h3,i,br,em,pre,code,ul,ol,li,dl,dt,dd]).
+-define(ALL_ELEMENTS,[a,p,'div',br,h1,h2,h3,i,em,pre,code,ul,ol,li,dl,dt,dd]).
%% inline elements are:
--define(INLINE,[i,br,em,code,a]).
+-define(INLINE,[i,em,code,a]).
-define(IS_INLINE(ELEM),(((ELEM) =:= a) orelse ((ELEM) =:= code)
- orelse ((ELEM) =:= i) orelse ((ELEM) =:= br)
- orelse ((ELEM) =:= em))).
+ orelse ((ELEM) =:= i) orelse ((ELEM) =:= em))).
%% non-inline elements are:
--define(BLOCK,[p,'div',pre,ul,ol,li,dl,dt,dd,h1,h2,h3]).
+-define(BLOCK,[p,'div',pre,br,ul,ol,li,dl,dt,dd,h1,h2,h3]).
-define(IS_BLOCK(ELEM),not ?IS_INLINE(ELEM)).
-define(IS_PRE(ELEM),(((ELEM) =:= pre))).
--type chunk_element_type() :: a | p | 'div' | i | br | em | pre | code | ul |
- ol | li | dl | dt | dd.
--type chunk_element_attr() :: {atom(),unicode:chardata()}.
--type chunk_element_attrs() :: [chunk_element_attr()].
+%% 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 chunk_elements() :: [chunk_element()].
-type chunk_element() :: {chunk_element_type(),chunk_element_attrs(),
chunk_elements()} | binary().
--type chunk_elements() :: [chunk_element()].
--type docs_v1() :: #docs_v1{}.
-
+-type chunk_element_attrs() :: [chunk_element_attr()].
+-type chunk_element_attr() :: {atom(),unicode:chardata()}.
+-type chunk_element_type() :: chunk_element_inline_type() | chunk_element_block_type().
+-type chunk_element_inline_type() :: a | code | em | i.
+-type chunk_element_block_type() :: p | 'div' | br | pre | ul |
+ ol | li | dl | dt | dd | h1 | h2 | h3.
-spec validate(Module) -> ok when
Module :: module() | docs_v1().
@@ -71,28 +74,80 @@ validate(#docs_v1{ module_doc = MDocs, docs = AllDocs }) ->
true = lists:all(fun(Elem) -> ?IS_INLINE(Elem) end, ?INLINE),
true = lists:all(fun(Elem) -> ?IS_BLOCK(Elem) end, ?BLOCK),
- _ = maps:map(fun(_Key,MDoc) -> validate(MDoc,[]) end, MDocs),
+ _ = validate_docs(MDocs),
lists:foreach(fun({_,_Anno, Sig, Docs, _Meta}) ->
- case lists:all(fun erlang:is_binary/1, Sig) of
- false -> throw({invalid_signature,Sig});
- true -> ok
- end,
- maps:map(fun(_Key,Doc) -> validate(Doc,[]) end, Docs)
- end, AllDocs),
+ case lists:all(fun erlang:is_binary/1, Sig) of
+ false -> throw({invalid_signature,Sig});
+ true -> ok
+ end,
+ validate_docs(Docs)
+ end, AllDocs),
+ ok.
+
+validate_docs(hidden) ->
+ ok;
+validate_docs(none) ->
+ ok;
+validate_docs(#{} = MDocs) ->
+ _ = maps:map(fun(_Key,MDoc) -> validate_docs(MDoc,[]) end, MDocs),
ok.
-validate([H|T],Path) when is_tuple(H) ->
- _ = validate(H,Path),
- validate(T,Path);
-validate({Tag,Attr,Content},Path) ->
+validate_docs([H|T],Path) when is_tuple(H) ->
+ _ = validate_docs(H,Path),
+ validate_docs(T,Path);
+validate_docs({br,Attr,Content} = Br,Path) ->
+ if Attr =:= [], Content =:= [] ->
+ ok;
+ true ->
+ throw({content_to_allowed_in_br,Br,Path})
+ end;
+validate_docs({Tag,Attr,Content},Path) ->
+
+ %% Test that we only have li's within ul and ol
+ case (Tag =/= li) andalso (length(Path) > 0) andalso ((hd(Path) =:= ul) orelse (hd(Path) =:= ol)) of
+ true ->
+ throw({only_li_allowed_within_ul_or_ol,Tag,Path});
+ _ ->
+ ok
+ end,
+
+ %% Test that we only have dd's and dt's within dl
+ case (Tag =/= dd) andalso (Tag =/= dt) andalso (length(Path) > 0) andalso (hd(Path) =:= dl) of
+ true ->
+ throw({only_dd_or_dt_allowed_within_dl,Tag,Path});
+ _ ->
+ ok
+ end,
+
+ %% Test that we do not have p's within p's
case Tag =:= p andalso lists:member(p, Path) of
true ->
- throw({nested,p,not_allowed});
+ throw({nested_p_not_allowed,Tag,Path});
+ false ->
+ ok
+ end,
+ %% Test that there are no block tags within a pre, h1, h2 or h3
+ case lists:member(pre,Path) or lists:member(h1,Path) or
+ lists:member(h2,Path) or lists:member(h3,Path) of
+ true when ?IS_BLOCK(Tag) ->
+ throw({cannot_put_block_tag_within_pre,Tag,Path});
+ _ ->
+ ok
+ end,
+ %% Test that a block tag is not within an inline tag
+ case lists:member(Tag,?BLOCK) of
+ true ->
+ case lists:any(fun(P) -> ?IS_INLINE(P) end, Path) of
+ true ->
+ throw({cannot_put_inline_tag_outside_block, Tag, Path});
+ false ->
+ ok
+ end;
false ->
ok
end,
case lists:member(Tag,?ALL_ELEMENTS) of
false ->
- throw({invalid_tag,Tag});
+ throw({invalid_tag,Tag,Path});
true ->
ok
end,
@@ -100,10 +155,10 @@ validate({Tag,Attr,Content},Path) ->
true -> ok;
false -> throw({invalid_attribute,{Tag,Attr}})
end,
- validate(Content,[Tag | Path]);
-validate([Chars | T], Path) when is_binary(Chars) ->
- validate(T, Path);
-validate([],_) ->
+ validate_docs(Content,[Tag | Path]);
+validate_docs([Chars | T], Path) when is_binary(Chars) ->
+ validate_docs(T, Path);
+validate_docs([],_) ->
ok.
%% Follows algorithm described here:
@@ -119,13 +174,13 @@ normalize(Docs) ->
normalize_trim(Bin,true) when is_binary(Bin) ->
%% Remove any whitespace (except \n) before or after a newline
- NoSpace = re:replace(Bin,"[^\\S\n]*\n+[^\\S\n]*","\n",[global]),
+ NoSpace = re:replace(Bin,"[^\\S\n]*\n+[^\\S\n]*","\n",[unicode,global]),
%% Replace any tabs with space
- NoTab = re:replace(NoSpace,"\t"," ",[global]),
+ NoTab = re:replace(NoSpace,"\t"," ",[unicode,global]),
%% Replace any newlines with space
- NoNewLine = re:replace(NoTab,"\\v"," ",[global]),
+ NoNewLine = re:replace(NoTab,"\\v"," ",[unicode,global]),
%% Replace any sequences of \s with a single " "
- re:replace(NoNewLine,"\\s+"," ",[global,{return,binary}]);
+ re:replace(NoNewLine,"\\s+"," ",[unicode,global,{return,binary}]);
normalize_trim(Bin,false) when is_binary(Bin) ->
Bin;
normalize_trim([{pre,Attr,Content}|T],Trim) ->
@@ -148,38 +203,41 @@ normalize_trim([],_Trim) ->
normalize_space([{Pre,Attr,Content}|T]) when ?IS_PRE(Pre) ->
[{Pre,Attr,trim_first_and_last(Content,$\n)} | normalize_space(T)];
normalize_space([{Block,Attr,Content}|T]) when ?IS_BLOCK(Block) ->
- [{Block,Attr,trim_first_and_last(trim_inline(Content),$ )} | normalize_space(T)];
-normalize_space([B]) when is_binary(B) ->
- trim_first_and_last([B],$ );
-normalize_space([E|T]) ->
- [E|normalize_space(T)];
+ [{Block,Attr,normalize_space(Content)} | normalize_space(T)];
normalize_space([]) ->
- [].
+ [];
+normalize_space(Elems) ->
+ {InlineElems, T} =
+ lists:splitwith(fun(E) ->
+ is_binary(E) orelse (is_tuple(E) andalso ?IS_INLINE(element(1,E)))
+ end, Elems),
+ trim_inline(InlineElems) ++ normalize_space(T).
trim_inline(Content) ->
{NewContent,_} = trim_inline(Content,false),
- NewContent.
+ trim_first_and_last(NewContent,$ ).
trim_inline([Bin|T],false) when is_binary(Bin) ->
LastElem = binary:at(Bin,byte_size(Bin)-1),
- {NewT, NewState} = trim_inline(T,LastElem =:= $ ),
- {[Bin | NewT],NewState};
+ case trim_inline(T,LastElem =:= $ ) of
+ {[B2 | NewT],NewState} when is_binary(B2) ->
+ {[<<Bin/binary,B2/binary>>|NewT],NewState};
+ {NewT, NewState} ->
+ {[Bin|NewT],NewState}
+ end;
trim_inline([<<" ">>|T],true) ->
- trim_inline(T,false);
+ trim_inline(T,true);
trim_inline([<<" ",Bin/binary>>|T],true) when is_binary(Bin) ->
- trim_inline([Bin | T],false);
+ trim_inline([Bin | T],true);
trim_inline([Bin|T],true) when is_binary(Bin) ->
trim_inline([Bin|T],false);
-trim_inline([{Elem,Attr,Content}|T],TrimSpace) when ?IS_INLINE(Elem) ->
+trim_inline([{Elem,Attr,Content}|T],TrimSpace) ->
{NewContent,ContentTrimSpace} = trim_inline(Content,TrimSpace),
{NewT,TTrimSpace} = trim_inline(T,ContentTrimSpace),
- {[{Elem,Attr,NewContent} | NewT], TTrimSpace};
-trim_inline([{Elem1,_A1,_C1} = B1,<<" ">>,{Elem2,_A2,_C2} = B2|T],TrimSpace)
- when ?IS_BLOCK(Elem1),?IS_BLOCK(Elem2) ->
- trim_inline([B1,B2|T],TrimSpace);
-trim_inline([{Elem,_Attr,_Content} = Block|T],_TrimSpace) when ?IS_BLOCK(Elem) ->
- [NewBlock] = normalize_space([Block]),
- {NewT,TTrimSpace} = trim_inline(T,false),
- {[NewBlock | NewT], TTrimSpace};
+ if NewContent == [] ->
+ {NewT, TTrimSpace};
+ true ->
+ {[{Elem,Attr,NewContent} | NewT], TTrimSpace}
+ end;
trim_inline([],TrimSpace) ->
{[],TrimSpace}.
@@ -205,6 +263,8 @@ trim_first([Bin|T],false,What) when is_binary(Bin) ->
end;
trim_first([{Elem,Attr,Content} = Tag|T],false,What) ->
case trim_first(Content,false,What) of
+ {[],true} ->
+ {T,true};
{NewContent,true} ->
{[{Elem,Attr,NewContent}|T],true};
{Content,false} ->
@@ -233,8 +293,12 @@ trim_last([{Elem,Attr,Content} = Tag|T],What) ->
{NewT,true} ->
{[Tag | NewT],true};
{T,false} ->
- {NewContent,NewState} = trim_last(Content,What),
- {[{Elem,Attr,NewContent}|T],NewState}
+ case trim_last(Content,What) of
+ {[],NewState} ->
+ {T,NewState};
+ {NewContent,NewState} ->
+ {[{Elem,Attr,NewContent}|T],NewState}
+ end
end;
trim_last([],_What) ->
{[],false}.
@@ -265,9 +329,9 @@ 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, metadata = MD } = D) ->
- render_docs([["\t",atom_to_binary(Module)]],
- get_local_doc(Module, ModuleDoc), MD, D).
+render(Module, #docs_v1{ module_doc = ModuleDoc } = D) ->
+ render_headers_and_docs([[{h2,[],[<<"\t",(atom_to_binary(Module))/binary>>]}]],
+ get_local_doc(Module, ModuleDoc), D).
-spec render(Module :: module(), Function :: function(), Docs :: docs_v1()) ->
unicode:chardata() | {error,function_missing}.
@@ -309,18 +373,13 @@ get_type_doc(Module, Type, Arity) ->
[{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().
-render_type(Module, #docs_v1{ docs = Docs } = D) ->
- render_type_signatures(Module,
- lists:filter(fun({{type, _, _},_Anno,_Sig,_Doc,_Meta}) ->
- true;
- (_) ->
- false
- end, Docs), D).
+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_docs(
+ render_typecb_docs(
lists:filter(fun({{type, T, _},_Anno,_Sig,_Doc,_Meta}) ->
T =:= Type;
(_) ->
@@ -330,13 +389,56 @@ render_type(_Module, Type, #docs_v1{ docs = 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) ->
- render_type_docs(
+ render_typecb_docs(
lists:filter(fun({{type, T, A},_Anno,_Sig,_Doc,_Meta}) ->
T =:= Type andalso A =:= Arity;
(_) ->
false
end, Docs), D).
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% API function for dealing with the callback documentation
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec get_callback_doc(Module :: module(), Callback :: atom(), Arity :: arity()) ->
+ [{{Callback,Arity}, Anno, Signature, chunk_elements(), Metadata}] when
+ Callback :: atom(),
+ Arity :: arity(),
+ Anno :: erl_anno:anno(),
+ Signature :: [binary()],
+ Metadata :: #{}.
+get_callback_doc(Module, Callback, Arity) ->
+ {ok, #docs_v1{ docs = Docs } } = code:get_doc(Module),
+ FnFunctions =
+ lists:filter(fun({{callback, T, A},_Anno,_Sig,_Doc,_Meta}) ->
+ T =:= Callback andalso A =:= Arity;
+ (_) ->
+ false
+ 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().
+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_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) ->
+ render_typecb_docs(
+ lists:filter(fun({{callback, T, A},_Anno,_Sig,_Doc,_Meta}) ->
+ T =:= Callback andalso A =:= Arity;
+ (_) ->
+ false
+ end, Docs), D).
%% Get the docs in the correct locale if it exists.
get_local_doc(MissingMod, Docs) when is_atom(MissingMod) ->
@@ -359,58 +461,127 @@ get_local_doc(Missing, None) when None =:= none; None =:= #{} ->
%%% Functions for rendering reference documentation
render_function([], _D) ->
{error,function_missing};
-render_function(FDocs, D) ->
- [render_docs(render_signature(Func), get_local_doc({F,A},Doc), Meta, D)
- || {{_,F,A},_Anno,_Sig,Doc,Meta} = Func <- lists:sort(FDocs)].
-
-%% Render the signature of either function or a type, or anything else really.
-render_signature({{_Type,_F,_A},_Anno,_Sig,_Docs,#{ signature := Specs }}) ->
- [erl_pp:attribute(Spec,[{encoding,utf8}]) || Spec <- Specs];
-render_signature({{_Type,_F,_A},_Anno,Sigs,_Docs,_Meta}) ->
- [Sig || Sig <- Sigs].
-
-render_since(#{ since := Vsn }) ->
- ["\n\nSince: ",Vsn];
-render_since(_) ->
+render_function(FDocs, #docs_v1{ docs = Docs } = D) ->
+ Grouping =
+ lists:foldl(
+ fun({_Group,_Anno,_Sig,_Doc,#{ equiv := Group }} = Func,Acc) ->
+ Members = maps:get(Group, Acc, []),
+ Acc#{ Group => [Func|Members] };
+ ({Group, _Anno, _Sig, _Doc, _Meta} = Func, Acc) ->
+ Members = maps:get(Group, Acc, []),
+ Acc#{ Group => [Func|Members] }
+ end, #{}, lists:sort(FDocs)),
+ lists:map(
+ fun({{_,F,A} = Group,Members}) ->
+ Signatures = lists:flatmap(fun render_signature/1,lists:reverse(Members)),
+ case lists:search(fun({_,_,_,Doc,_}) ->
+ Doc =/= #{}
+ end, Members) of
+ {value, {_,_,_,Doc,_Meta}} ->
+ render_headers_and_docs(Signatures, get_local_doc({F,A},Doc), D);
+ false ->
+ case lists:keyfind(Group, 1, Docs) of
+ false ->
+ render_headers_and_docs(Signatures, get_local_doc({F,A},none), D);
+ {_,_,_,Doc,_} ->
+ render_headers_and_docs(Signatures, get_local_doc({F,A},Doc), D)
+ end
+ end
+ end, maps:to_list(Grouping)).
+
+%% Render the signature of either function, type, or anything else really.
+render_signature({{_Type,_F,_A},_Anno,_Sigs,_Docs,#{ signature := Specs } = Meta}) ->
+ lists:flatmap(
+ fun(ASTSpec) ->
+ PPSpec = erl_pp:attribute(ASTSpec,[{encoding,utf8}]),
+ Spec =
+ case ASTSpec of
+ {_Attribute, _Line, opaque, _} ->
+ %% We do not want show the internals of the opaque type
+ hd(string:split(PPSpec,"::"));
+ _ ->
+ PPSpec
+ end,
+ BinSpec =
+ unicode:characters_to_binary(
+ string:trim(Spec, trailing, "\n")),
+ [{pre,[],[{em,[],BinSpec}]}|render_meta(Meta)]
+ end, Specs);
+render_signature({{_Type,_F,_A},_Anno,Sigs,_Docs,Meta}) ->
+ lists:flatmap(
+ fun(Sig) ->
+ [{h2,[],[<<"  "/utf8,Sig/binary>>]}|render_meta(Meta)]
+ end, Sigs).
+
+render_meta(M) ->
+ case render_meta_(M) of
+ [] -> [];
+ Meta ->
+ [[{dl,[],Meta}]]
+ end.
+render_meta_(#{ since := Vsn } = M) ->
+ [{dt,[],<<"Since">>},{dd,[],[Vsn]}
+ | render_meta_(maps:remove(since, M))];
+render_meta_(#{ deprecated := Depr } = M) ->
+ [{dt,[],<<"Deprecated">>},{dd,[],[Depr]}
+ | render_meta_(maps:remove(deprecated, M))];
+render_meta_(_) ->
[].
-render_docs(Headers, DocContents, MD, D = #config{}) ->
- init_ansi(D),
- try
- {Doc,_} = trimnl(render_docs(DocContents,[],0,2,D)),
- [sansi(bold),
- [io_lib:format("~n~ts",[Header]) || Header <- Headers],
- ransi(bold),
- render_since(MD),
- io_lib:format("~n~n~ts",[Doc])]
- after
- clean_ansi()
- end;
-render_docs(Headers, DocContents, MD, D) ->
- render_docs(Headers, DocContents, MD, #config{ docs = D }).
+render_headers_and_docs(Headers, DocContents, D) ->
+ ["\n",render_docs(
+ lists:flatmap(
+ fun(Header) ->
+ [{br,[],[]},Header]
+ end,Headers), 0, D),
+ "\n",
+ render_docs(DocContents,2,D)].
+
+%%% Functions for rendering type/callback documentation
+render_signature_listing(Module, Type, #docs_v1{ docs = Docs } = D) ->
+ Slogan = [{h2,[],[<<"\t",(atom_to_binary(Module))/binary>>]},{br,[],[]}],
+ case lists:filter(fun({{T, _, _},_Anno,_Sig,_Doc,_Meta}) ->
+ Type =:= T
+ end, Docs) of
+ [] ->
+ render_docs(
+ Slogan ++ [<<"There are no ",(atom_to_binary(Type))/binary,"s "
+ "in this module">>], D);
+ Headers ->
+ Hdr = lists:flatmap(
+ fun(Header) ->
+ [{br,[],[]},render_signature(Header)]
+ end,Headers),
+ render_docs(
+ Slogan ++
+ [{p,[],[<<"These ",(atom_to_binary(Type))/binary,"s "
+ "are documented in this module:">>]},
+ {br,[],[]}, Hdr], D)
+ end.
+
+render_typecb_docs([], _D) ->
+ {error,type_missing};
+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 }).
-%%% Functions for rendering type documentation
-render_type_signatures(Module, Types, D = #config{}) ->
+%%% General rendering functions
+render_docs(DocContents, D) ->
+ render_docs(DocContents, 0, D).
+render_docs(DocContents, Ind, D = #config{}) ->
init_ansi(D),
try
- [sansi(bold),"\t",atom_to_list(Module),ransi(bold),"\n\n",
- [render_signature(Type) || Type <- Types ]]
+ {Doc,_} = trimnl(render_docs(DocContents, [], 0, Ind, D)),
+ Doc
after
clean_ansi()
end;
-render_type_signatures(Module, Types, D) ->
- render_type_signatures(Module, Types, #config{ docs = D }).
-
-render_type_docs([], _D) ->
- {error,type_missing};
-render_type_docs(Types, #config{} = D) when is_list(Types) ->
- [render_type_docs(Type, D) || Type <- Types];
-render_type_docs({{_,F,A},_,_Sig,Docs,Meta} = Type, #config{} = D) ->
- render_docs(render_signature(Type), get_local_doc({F,A},Docs), Meta, D);
-render_type_docs(Docs, D) ->
- render_type_docs(Docs, #config{ docs = D }).
+render_docs(DocContents, Ind, D) ->
+ render_docs(DocContents, Ind, #config{ docs = D }).
-%%% General rendering functions
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]),
@@ -443,10 +614,10 @@ render_docs(Elem,State,Pos,Ind,D) ->
{unicode:chardata(), Pos :: non_neg_integer()}.
render_element({IgnoreMe,_,Content}, State, Pos, Ind,D)
- when IgnoreMe =:= a; IgnoreMe =:= anno ->
+ when IgnoreMe =:= a ->
render_docs(Content, State, Pos, Ind,D);
-%% Catch h1, h2 and h3 before the padding is done as there reset padding
+%% Catch h1, h2 and h3 before the padding is done as they reset padding
render_element({h1,_,Content},State,0 = Pos,_Ind,D) ->
trimnlnl(render_element({code,[],[{em,[],Content}]}, State, Pos, 0, D));
render_element({h2,_,Content},State,0 = Pos,_Ind,D) ->
@@ -461,7 +632,7 @@ render_element({'div',[{class,What}],Content},State,Pos,Ind,D) ->
{Docs,_} = render_docs(Content, ['div'|State], 0, Ind+2, D),
trimnlnl([pad(Ind - Pos),string:titlecase(What),":\n",Docs]);
render_element({Tag,_,Content},State,Pos,Ind,D) when Tag =:= p; Tag =:= 'div' ->
- trimnlnl(render_docs(Content, [Tag|State], Pos, Ind,D));
+ 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]),
@@ -481,8 +652,8 @@ render_element({i,_,Content},State,Pos,Ind,D) ->
%% Just ignore i as ansi does not have cursive style
render_docs(Content, State, Pos, Ind,D);
-render_element({br,[],[]},_State,_Pos,_Ind,_D) ->
- nl("");
+render_element({br,[],[]},_State,Pos,_Ind,_D) ->
+ {"",Pos};
render_element({em,_,Content},State,Pos,Ind,D) ->
Bold = sansi(bold),
@@ -550,7 +721,13 @@ render_element(B, State, Pos, Ind,#config{ io_columns = Cols }) when is_binary(B
end;
render_element({Tag,Attr,Content}, State, Pos, Ind,D) ->
- throw({unhandled,{Tag,Attr,Content,Pos,Ind}}),
+ case lists:member(Tag,?ALL_ELEMENTS) of
+ true ->
+ throw({unhandled_element,Tag,Attr,Content});
+ false ->
+ %% We ignore tags that we do not care about
+ ok
+ end,
render_docs(Content, State, Pos, Ind,D).
render_words(Words,[_,types|State],Pos,Ind,Acc,Cols) ->
@@ -560,8 +737,10 @@ render_words(Words,[_,types|State],Pos,Ind,Acc,Cols) ->
render_words([Word|T],State,Pos,Ind,Acc,Cols) when is_binary(Word) ->
WordLength = string:length(Word),
NewPos = WordLength + Pos,
+ %% We do not want to add a newline if this word is only a punctuation
+ IsPunct = is_tuple(re:run(Word,"^\\W$",[unicode])),
if
- NewPos > (Cols - 10 - Ind) ->
+ 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 ->
@@ -601,9 +780,14 @@ get_bullet(_State,latin1) ->
<<" * ">>;
get_bullet(State,unicode) ->
%% Fancy bullet point logic!
- lists:nth(length([l || l <- State]),
- [<<" • "/utf8>>,<<" ○ "/utf8>>,
- <<" â—¼ "/utf8>>,<<" â—» "/utf8>>]).
+ case length([l || l <- State]) of
+ Level when Level > 4 ->
+ get_bullet(State, latin1);
+ Level ->
+ lists:nth(Level,
+ [<<" • "/utf8>>,<<" ○ "/utf8>>,
+ <<" â—¼ "/utf8>>,<<" â—» "/utf8>>])
+ end.
% Look for the length of the last line of a string
lastline(Str) ->
diff --git a/lib/stdlib/src/stdlib.app.src b/lib/stdlib/src/stdlib.app.src
index 774f7eaa9c..b59e3b28c0 100644
--- a/lib/stdlib/src/stdlib.app.src
+++ b/lib/stdlib/src/stdlib.app.src
@@ -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/lib/stdlib/src/stdlib.appup.src b/lib/stdlib/src/stdlib.appup.src
index 6d6ee14d29..85fba9ebbd 100644
--- a/lib/stdlib/src/stdlib.appup.src
+++ b/lib/stdlib/src/stdlib.appup.src
@@ -21,6 +21,7 @@
%% versions from the following OTP releases:
%% - OTP 21
%% - OTP 22
+%% - OTP 23
%%
%% We also allow upgrade from, and downgrade to all
%% versions that have branched off from the above
@@ -35,6 +36,7 @@
{<<"^3\\.11\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^3\\.12$">>,[restart_new_emulator]},
{<<"^3\\.12\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^3\\.12\\.1(?:\\.[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]},
@@ -59,6 +61,7 @@
{<<"^3\\.11\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^3\\.12$">>,[restart_new_emulator]},
{<<"^3\\.12\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^3\\.12\\.1(?:\\.[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/test/digraph_SUITE.erl b/lib/stdlib/test/digraph_SUITE.erl
index b5d3452616..ce0bc90f1c 100644
--- a/lib/stdlib/test/digraph_SUITE.erl
+++ b/lib/stdlib/test/digraph_SUITE.erl
@@ -31,7 +31,7 @@
init_per_group/2,end_per_group/2]).
-export([opts/1, degree/1, path/1, cycle/1, vertices/1,
- edges/1, data/1, otp_3522/1, otp_3630/1, otp_8066/1]).
+ edges/1, data/1, otp_3522/1, otp_3630/1, otp_8066/1, vertex_names/1]).
-export([spawn_graph/2]).
@@ -41,10 +41,10 @@ suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
[opts, degree, path, cycle, {group, misc},
- {group, tickets}].
+ {group, tickets}, vertex_names].
groups() ->
- [{misc, [], [vertices, edges, data]},
+ [{misc, [], [vertices, edges, data, vertex_names]},
{tickets, [], [otp_3522, otp_3630, otp_8066]}].
init_per_suite(Config) ->
@@ -337,6 +337,51 @@ otp_8066(Config) when is_list(Config) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+vertex_names(Config) when is_list(Config) ->
+ %% Check that a node named '_' does not lead to wildcard matches
+ %% in ets.
+
+ G = digraph:new([acyclic]),
+ A = digraph:add_vertex(G, 'A'),
+ B = digraph:add_vertex(G, '_'),
+ AB = digraph:add_edge(G, A, B),
+
+ %% Link A -> B
+ 1 = digraph:out_degree(G, A),
+ 1 = digraph:in_degree(G, B),
+ 0 = digraph:out_degree(G, B),
+ 0 = digraph:in_degree(G, A),
+ [B] = digraph:out_neighbours(G, A),
+ [A] = digraph:in_neighbours(G, B),
+ [] = digraph:out_neighbours(G, B),
+ [] = digraph:in_neighbours(G, A),
+ [AB] = digraph:out_edges(G, A),
+ [AB] = digraph:in_edges(G, B),
+ [] = digraph:out_edges(G, B),
+ [] = digraph:in_edges(G, A),
+
+ %% Reverse the edge
+ digraph:del_edge(G, AB),
+ BA = digraph:add_edge(G, B, A),
+
+ 1 = digraph:out_degree(G, B),
+ 1 = digraph:in_degree(G, A),
+ 0 = digraph:out_degree(G, A),
+ 0 = digraph:in_degree(G, B),
+ [A] = digraph:out_neighbours(G, B),
+ [B] = digraph:in_neighbours(G, A),
+ [] = digraph:out_neighbours(G, A),
+ [] = digraph:in_neighbours(G, B),
+ [BA] = digraph:out_edges(G, B),
+ [BA] = digraph:in_edges(G, A),
+ [] = digraph:out_edges(G, A),
+ [] = digraph:in_edges(G, B),
+
+ digraph:delete(G),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
sane(G) ->
sane1(G),
erase(sane) =:= undefined.
diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl
index 1bde1b3d74..76dee868e9 100644
--- a/lib/stdlib/test/gen_statem_SUITE.erl
+++ b/lib/stdlib/test/gen_statem_SUITE.erl
@@ -453,19 +453,24 @@ stop7(Config) ->
stop8(Config) ->
Node = gen_statem_stop8,
{ok,NodeName} = ct_slave:start(Node),
- Dir = filename:dirname(code:which(?MODULE)),
- rpc:call(NodeName, code, add_path, [Dir]),
- {ok,Pid} =
- rpc:call(
- NodeName, gen_statem,start,
- [?MODULE,start_arg(Config, []),[]]),
- ok = gen_statem:stop(Pid),
- false = rpc:call(NodeName, erlang, is_process_alive, [Pid]),
- noproc =
- ?EXPECT_FAILURE(gen_statem:stop(Pid), Reason1),
- {ok,NodeName} = ct_slave:stop(Node),
+ Statem =
+ try
+ Dir = filename:dirname(code:which(?MODULE)),
+ rpc:block_call(NodeName, code, add_path, [Dir]),
+ {ok,Pid} =
+ rpc:block_call(
+ NodeName, gen_statem,start,
+ [?MODULE,start_arg(Config, []),[]]),
+ ok = gen_statem:stop(Pid),
+ false = rpc:block_call(NodeName, erlang, is_process_alive, [Pid]),
+ noproc =
+ ?EXPECT_FAILURE(gen_statem:stop(Pid), Reason1),
+ Pid
+ after
+ {ok,NodeName} = ct_slave:stop(Node)
+ end,
{{nodedown,NodeName},{sys,terminate,_}} =
- ?EXPECT_FAILURE(gen_statem:stop(Pid), Reason2),
+ ?EXPECT_FAILURE(gen_statem:stop(Statem), Reason2),
ok.
%% Registered name on remote node
@@ -474,21 +479,26 @@ stop9(Config) ->
LocalSTM = {local,Name},
Node = gen_statem__stop9,
{ok,NodeName} = ct_slave:start(Node),
- STM = {Name,NodeName},
- Dir = filename:dirname(code:which(?MODULE)),
- rpc:call(NodeName, code, add_path, [Dir]),
- {ok,Pid} =
- rpc:call(
- NodeName, gen_statem, start,
- [LocalSTM,?MODULE,start_arg(Config, []),[]]),
- ok = gen_statem:stop(STM),
- undefined = rpc:call(NodeName,erlang,whereis,[Name]),
- false = rpc:call(NodeName,erlang,is_process_alive,[Pid]),
- noproc =
- ?EXPECT_FAILURE(gen_statem:stop(STM), Reason1),
- {ok,NodeName} = ct_slave:stop(Node),
+ Statem =
+ try
+ STM = {Name,NodeName},
+ Dir = filename:dirname(code:which(?MODULE)),
+ rpc:block_call(NodeName, code, add_path, [Dir]),
+ {ok,Pid} =
+ rpc:block_call(
+ NodeName, gen_statem, start,
+ [LocalSTM,?MODULE,start_arg(Config, []),[]]),
+ ok = gen_statem:stop(STM),
+ undefined = rpc:block_call(NodeName,erlang,whereis,[Name]),
+ false = rpc:block_call(NodeName,erlang,is_process_alive,[Pid]),
+ noproc =
+ ?EXPECT_FAILURE(gen_statem:stop(STM), Reason1),
+ STM
+ after
+ {ok,NodeName} = ct_slave:stop(Node)
+ end,
{{nodedown,NodeName},{sys,terminate,_}} =
- ?EXPECT_FAILURE(gen_statem:stop(STM), Reason2),
+ ?EXPECT_FAILURE(gen_statem:stop(Statem), Reason2),
ok.
%% Globally registered name on remote node
@@ -496,18 +506,21 @@ stop10(Config) ->
Node = gen_statem_stop10,
STM = {global,to_stop},
{ok,NodeName} = ct_slave:start(Node),
- Dir = filename:dirname(code:which(?MODULE)),
- rpc:call(NodeName,code,add_path,[Dir]),
- {ok,Pid} =
- rpc:call(
- NodeName, gen_statem, start,
- [STM,?MODULE,start_arg(Config, []),[]]),
- global:sync(),
- ok = gen_statem:stop(STM),
- false = rpc:call(NodeName, erlang, is_process_alive, [Pid]),
- noproc =
- ?EXPECT_FAILURE(gen_statem:stop(STM), Reason1),
- {ok,NodeName} = ct_slave:stop(Node),
+ try
+ Dir = filename:dirname(code:which(?MODULE)),
+ rpc:block_call(NodeName,code,add_path,[Dir]),
+ {ok,Pid} =
+ rpc:block_call(
+ NodeName, gen_statem, start,
+ [STM,?MODULE,start_arg(Config, []),[]]),
+ global:sync(),
+ ok = gen_statem:stop(STM),
+ false = rpc:block_call(NodeName, erlang, is_process_alive, [Pid]),
+ noproc =
+ ?EXPECT_FAILURE(gen_statem:stop(STM), Reason1)
+ after
+ {ok,NodeName} = ct_slave:stop(Node)
+ end,
noproc =
?EXPECT_FAILURE(gen_statem:stop(STM), Reason2),
ok.
@@ -521,10 +534,10 @@ abnormal1(Config) ->
gen_statem:start(LocalSTM, ?MODULE, start_arg(Config, []), []),
%% timeout call.
- delayed = gen_statem:call(Name, {delayed_answer,1}, 100),
+ delayed = gen_statem:call(Name, {delayed_answer,100}, 2000),
{timeout,_} =
?EXPECT_FAILURE(
- gen_statem:call(Name, {delayed_answer,1000}, 10),
+ gen_statem:call(Name, {delayed_answer,2000}, 100),
Reason),
ok = gen_statem:stop(Name),
?t:sleep(1100),
@@ -1424,7 +1437,7 @@ hibernate(Config) ->
{ok,Pid0} =
gen_statem:start_link(
?MODULE, start_arg(Config, hiber_now), []),
- is_in_erlang_hibernate(Pid0),
+ wait_erlang_hibernate(Pid0),
stop_it(Pid0),
receive
{'EXIT',Pid0,normal} -> ok
@@ -1437,23 +1450,23 @@ hibernate(Config) ->
true = ({current_function,{erlang,hibernate,3}} =/=
erlang:process_info(Pid,current_function)),
hibernating = gen_statem:call(Pid, hibernate_sync),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
good_morning = gen_statem:call(Pid, wakeup_sync),
is_not_in_erlang_hibernate(Pid),
hibernating = gen_statem:call(Pid, hibernate_sync),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
please_just_five_more = gen_statem:call(Pid, snooze_sync),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
good_morning = gen_statem:call(Pid, wakeup_sync),
is_not_in_erlang_hibernate(Pid),
ok = gen_statem:cast(Pid, hibernate_async),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
ok = gen_statem:cast(Pid, wakeup_async),
is_not_in_erlang_hibernate(Pid),
ok = gen_statem:cast(Pid, hibernate_async),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
ok = gen_statem:cast(Pid, snooze_async),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
ok = gen_statem:cast(Pid, wakeup_async),
is_not_in_erlang_hibernate(Pid),
@@ -1461,14 +1474,14 @@ hibernate(Config) ->
true =
({current_function,{erlang,hibernate,3}} =/=
erlang:process_info(Pid, current_function)),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
'alive!' = gen_statem:call(Pid, 'alive?'),
true =
({current_function,{erlang,hibernate,3}} =/=
erlang:process_info(Pid, current_function)),
Pid ! hibernate_now,
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
'alive!' = gen_statem:call(Pid, 'alive?'),
true =
@@ -1476,34 +1489,34 @@ hibernate(Config) ->
erlang:process_info(Pid, current_function)),
hibernating = gen_statem:call(Pid, hibernate_sync),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
good_morning = gen_statem:call(Pid, wakeup_sync),
is_not_in_erlang_hibernate(Pid),
hibernating = gen_statem:call(Pid, hibernate_sync),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
please_just_five_more = gen_statem:call(Pid, snooze_sync),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
good_morning = gen_statem:call(Pid, wakeup_sync),
is_not_in_erlang_hibernate(Pid),
ok = gen_statem:cast(Pid, hibernate_async),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
ok = gen_statem:cast(Pid, wakeup_async),
is_not_in_erlang_hibernate(Pid),
ok = gen_statem:cast(Pid, hibernate_async),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
ok = gen_statem:cast(Pid, snooze_async),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
ok = gen_statem:cast(Pid, wakeup_async),
is_not_in_erlang_hibernate(Pid),
hibernating = gen_statem:call(Pid, hibernate_sync),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
sys:suspend(Pid),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
sys:resume(Pid),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
receive after 1000 -> ok end,
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
good_morning = gen_statem:call(Pid, wakeup_sync),
is_not_in_erlang_hibernate(Pid),
@@ -1519,15 +1532,16 @@ hibernate(Config) ->
%% Auto-hibernation timeout
auto_hibernate(Config) ->
OldFl = process_flag(trap_exit, true),
- HibernateAfterTimeout = 100,
+ HibernateAfterTimeout = 1000,
{ok,Pid} =
gen_statem:start_link(
- ?MODULE, start_arg(Config, []), [{hibernate_after, HibernateAfterTimeout}]),
+ ?MODULE, start_arg(Config, []),
+ [{hibernate_after, HibernateAfterTimeout}]),
%% After init test
is_not_in_erlang_hibernate(Pid),
timer:sleep(HibernateAfterTimeout),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
%% After info test
Pid ! {hping, self()},
receive
@@ -1538,7 +1552,7 @@ auto_hibernate(Config) ->
end,
is_not_in_erlang_hibernate(Pid),
timer:sleep(HibernateAfterTimeout),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
%% After cast test
ok = gen_statem:cast(Pid, {hping, self()}),
receive
@@ -1549,42 +1563,42 @@ auto_hibernate(Config) ->
end,
is_not_in_erlang_hibernate(Pid),
timer:sleep(HibernateAfterTimeout),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
%% After call test
hpong = gen_statem:call(Pid, hping),
is_not_in_erlang_hibernate(Pid),
timer:sleep(HibernateAfterTimeout),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
%% Timer test 1
- TimerTimeout1 = 50,
- ok = gen_statem:call(Pid, {arm_htimer, self(), TimerTimeout1}),
+ TimerTimeout1 = HibernateAfterTimeout div 2,
+ ok = gen_statem:call(Pid, {start_htimer, self(), TimerTimeout1}),
is_not_in_erlang_hibernate(Pid),
timer:sleep(TimerTimeout1),
is_not_in_erlang_hibernate(Pid),
receive
- {Pid, htimer_armed} ->
+ {Pid, htimer_timeout} ->
ok
after 1000 ->
ct:fail(timer1)
end,
is_not_in_erlang_hibernate(Pid),
timer:sleep(HibernateAfterTimeout),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
%% Timer test 2
- TimerTimeout2 = 150,
- ok = gen_statem:call(Pid, {arm_htimer, self(), TimerTimeout2}),
+ TimerTimeout2 = HibernateAfterTimeout * 2,
+ ok = gen_statem:call(Pid, {start_htimer, self(), TimerTimeout2}),
is_not_in_erlang_hibernate(Pid),
timer:sleep(HibernateAfterTimeout),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
receive
- {Pid, htimer_armed} ->
+ {Pid, htimer_timeout} ->
ok
- after 1000 ->
+ after TimerTimeout2 ->
ct:fail(timer2)
end,
is_not_in_erlang_hibernate(Pid),
timer:sleep(HibernateAfterTimeout),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
stop_it(Pid),
process_flag(trap_exit, OldFl),
receive
@@ -1594,38 +1608,38 @@ auto_hibernate(Config) ->
end,
ok = verify_empty_msgq().
-is_in_erlang_hibernate(Pid) ->
+
+wait_erlang_hibernate(Pid) ->
receive after 1 -> ok end,
- is_in_erlang_hibernate_1(200, Pid).
+ wait_erlang_hibernate_1(200, Pid).
-is_in_erlang_hibernate_1(0, Pid) ->
+wait_erlang_hibernate_1(0, Pid) ->
ct:log("~p\n", [erlang:process_info(Pid, current_function)]),
- ct:fail(not_in_erlang_hibernate_3);
-is_in_erlang_hibernate_1(N, Pid) ->
+ ct:fail(should_be_in_erlang_hibernate_3);
+wait_erlang_hibernate_1(N, Pid) ->
{current_function,MFA} = erlang:process_info(Pid, current_function),
case MFA of
{erlang,hibernate,3} ->
ok;
_ ->
receive after 10 -> ok end,
- is_in_erlang_hibernate_1(N-1, Pid)
+ wait_erlang_hibernate_1(N-1, Pid)
end.
is_not_in_erlang_hibernate(Pid) ->
receive after 1 -> ok end,
is_not_in_erlang_hibernate_1(200, Pid).
-is_not_in_erlang_hibernate_1(0, Pid) ->
- ct:log("~p\n", [erlang:process_info(Pid, current_function)]),
- ct:fail(not_in_erlang_hibernate_3);
+is_not_in_erlang_hibernate_1(0, _Pid) ->
+ ct:fail(should_not_be_in_erlang_hibernate_3);
is_not_in_erlang_hibernate_1(N, Pid) ->
{current_function,MFA} = erlang:process_info(Pid, current_function),
case MFA of
- {erlang,hibernate,3} ->
+ {erlang,hibernate,3} ->
receive after 10 -> ok end,
is_not_in_erlang_hibernate_1(N-1, Pid);
- _ ->
- ok
+ _ ->
+ ok
end.
@@ -2340,13 +2354,13 @@ init(stop_shutdown) ->
{stop,shutdown};
init(sleep) ->
?t:sleep(1000),
- {ok,idle,data};
+ init_sup({ok,idle,data});
init(hiber) ->
- {ok,hiber_idle,[]};
+ init_sup({ok,hiber_idle,[]});
init(hiber_now) ->
- {ok,hiber_idle,[],[hibernate]};
+ init_sup({ok,hiber_idle,[],[hibernate]});
init({data, Data}) ->
- {ok,idle,Data};
+ init_sup({ok,idle,Data});
init({callback_mode,CallbackMode,Arg}) ->
ets:new(?MODULE, [named_table,private]),
ets:insert(?MODULE, {callback_mode,CallbackMode}),
@@ -2356,14 +2370,35 @@ init({map_statem,#{init := Init}=Machine,Modes}) ->
ets:insert(?MODULE, {callback_mode,[handle_event_function|Modes]}),
case Init() of
{ok,State,Data,Ops} ->
- {ok,State,[Data|Machine],Ops};
+ init_sup({ok,State,[Data|Machine],Ops});
{ok,State,Data} ->
- {ok,State,[Data|Machine]};
+ init_sup({ok,State,[Data|Machine]});
Other ->
- Other
+ init_sup(Other)
end;
init([]) ->
- {ok,idle,data}.
+ init_sup({ok,idle,data}).
+
+%% Supervise state machine parent i.e the test case, and if it dies
+%% (fails due to some reason), kill the state machine,
+%% just to not leak resources (process, name, ETS table, etc...)
+%%
+init_sup(Result) ->
+ Parent = gen:get_parent(),
+ Statem = self(),
+ _Supervisor =
+ spawn(
+ fun () ->
+ StatemRef = monitor(process, Statem),
+ ParentRef = monitor(process, Parent),
+ receive
+ {'DOWN', StatemRef, _, _, Reason} ->
+ exit(Reason);
+ {'DOWN', ParentRef, _, _, _} ->
+ exit(Statem, kill)
+ end
+ end),
+ Result.
callback_mode() ->
try ets:lookup(?MODULE, callback_mode) of
@@ -2396,10 +2431,10 @@ idle(cast, {hping,Pid}, Data) ->
{keep_state, Data};
idle({call, From}, hping, _Data) ->
{keep_state_and_data, [{reply, From, hpong}]};
-idle({call, From}, {arm_htimer, Pid, Timeout}, _Data) ->
- {keep_state_and_data, [{reply, From, ok}, {timeout, Timeout, {arm_htimer, Pid}}]};
-idle(timeout, {arm_htimer, Pid}, _Data) ->
- Pid ! {self(), htimer_armed},
+idle({call, From}, {start_htimer, Pid, Timeout}, _Data) ->
+ {keep_state_and_data, [{reply, From, ok}, {timeout, Timeout, {htimer, Pid}}]};
+idle(timeout, {htimer, Pid}, _Data) ->
+ Pid ! {self(), htimer_timeout},
keep_state_and_data;
idle(cast, {connect,Pid}, Data) ->
Pid ! accept,
diff --git a/lib/stdlib/test/gen_statem_SUITE_data/oc_statem.erl b/lib/stdlib/test/gen_statem_SUITE_data/oc_statem.erl
index 1bcd08867f..1de7de527b 100644
--- a/lib/stdlib/test/gen_statem_SUITE_data/oc_statem.erl
+++ b/lib/stdlib/test/gen_statem_SUITE_data/oc_statem.erl
@@ -31,6 +31,24 @@ start(Opts) ->
gen_statem:start({local, ?MODULE}, ?MODULE, [], Opts).
init([]) ->
+ %% Supervise state machine parent i.e the test case, and if it dies
+ %% (fails due to some reason), kill the state machine,
+ %% just to not leak resources (process, name, ETS table, etc...)
+ %%
+ Parent = gen:get_parent(),
+ Statem = self(),
+ _Supervisor =
+ spawn(
+ fun () ->
+ StatemRef = monitor(process, Statem),
+ ParentRef = monitor(process, Parent),
+ receive
+ {'DOWN', StatemRef, _, _, Reason} ->
+ exit(Reason);
+ {'DOWN', ParentRef, _, _, _} ->
+ exit(Statem, kill)
+ end
+ end),
{ok, start, #{}}.
callback_mode() ->
diff --git a/lib/stdlib/test/property_test/shell_docs_prop.erl b/lib/stdlib/test/property_test/shell_docs_prop.erl
new file mode 100644
index 0000000000..83b62e8837
--- /dev/null
+++ b/lib/stdlib/test/property_test/shell_docs_prop.erl
@@ -0,0 +1,135 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 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%
+%%
+-module(shell_docs_prop).
+-export([prop_render/0]).
+
+%%% This will include the .hrl file for the installed testing tool:
+-include_lib("common_test/include/ct_property_test.hrl").
+-include_lib("kernel/include/eep48.hrl").
+-compile([export_all]).
+
+prop_render() ->
+ numtests(10000,
+ ?FORALL(Doc,
+ ?LET(Blocks,blocks(),
+ #docs_v1{ module_doc = #{ <<"en">> => Blocks }, docs = [] }
+ ),
+ begin
+ try
+ shell_docs:render(test, Doc),
+ shell_docs:validate(Doc),
+ N1 = shell_docs:normalize(maps:get(<<"en">>,Doc#docs_v1.module_doc)),
+ N1 = shell_docs:normalize(N1),
+ true
+ catch C:E:ST ->
+ ct:pal("~p ~p:~p ~p~n",[Doc,C,E,ST]),
+ false
+ end
+ end)).
+
+-define(LIMIT,5).
+
+blocks() ->
+ blocks([]).
+
+blocks([l|_] = S) ->
+ ?LAZY(
+ frequency(
+ [{2,[]},
+ {2,[{li,[],blocks([li | S])}]}])
+ );
+blocks([dl|_] = S) ->
+ ?LAZY(
+ frequency(
+ [{2,[]},
+ {2,[{dt,[],blocks([dt | S])}]},
+ {2,[{dd,[],blocks([dd | S])}]}])
+ );
+blocks(S) ->
+ ?LAZY(
+ frequency(
+ [{?LIMIT div 2,[]},
+ {max(1,?LIMIT - length(S)),
+ ?LET(Lst,[block(S)|blocks(S)],lists:flatten(Lst))},
+ {max(1,?LIMIT - length(S)),
+ ?LET(Lst,[inlines()|blocks(S)],lists:flatten(Lst))}
+ ]
+ )).
+
+inlines() ->
+ inlines([]).
+inlines(S) ->
+ ?LAZY(
+ frequency(
+ [{?LIMIT,[]},
+ {max(1,?LIMIT - length(S)),[inline(S)|inlines(S)]}
+ ]
+ )).
+
+block(S) ->
+ frequency(
+ fmax('div',3,S,{'div',oneof([[],[{class,<<"Warning">>}]]), blocks(['div'|S])}) ++
+ fmax(p,1,S,{p,[],blocks([p|S])}) ++
+ fmax(l,3,S,{ul,[],blocks([l|S])}) ++
+ fmax(l,3,S,{ol,[],blocks([l|S])}) ++
+ fmax(dl,3,S,{dl,[],blocks([dl|S])}) ++
+ fmax(['div',l,dl],0,S,{h1,[],inlines(['div'|S])}) ++
+ fmax(['div',l,dl],0,S,{h2,[],inlines(['div'|S])}) ++
+ fmax(['div',l,dl],1,S,{h3,[],inlines(['div'|S])}) ++
+ [{5,{br,[],[]}}]
+ ).
+
+inline(S) ->
+ frequency(
+ fmax(i,1,S,{i,[],?LAZY(inlines([i|S]))}) ++
+ fmax(code,1,S,{code,[],?LAZY(inlines([code|S]))}) ++
+ fmax(a,1,S,{a,[],?LAZY(inlines([a|S]))}) ++
+ fmax(em,1,S,{em,[],?LAZY(inlines([em|S]))}) ++
+ [{10,characters()}]).
+
+characters() ->
+ ?LET(Str,list(frequency([{10,printable_character()},{1,char()}])),
+ unicode:characters_to_binary(Str)).
+
+printable_character() ->
+ oneof([integer($\040,$\176),
+ integer(16#A0, 16#D800-1),
+ integer(16#DFFF+1,16#FFFE-1),
+ integer(16#FFFF+1,16#10FFFF),
+ $\n,$\r,$\t,$\v,$\b,$\f,$\e]).
+
+fmax(What,Depth,S,E) when not is_list(What) ->
+ fmax([What],Depth,S,E);
+fmax(What,Depth,S,E) ->
+ Cnt =
+ lists:foldl(
+ fun(E,Cnt) ->
+ case lists:member(E,What) of
+ true ->
+ Cnt+1;
+ false ->
+ Cnt
+ end
+ end, 0, S),
+ if Depth-Cnt =< 0 ->
+ [];
+ true ->
+ [{10 - (Depth-Cnt),E}]
+ end.
diff --git a/lib/stdlib/test/re_SUITE_data/testoutput2 b/lib/stdlib/test/re_SUITE_data/testoutput2
index 4ccda27201..f5d32d6ad0 100644
--- a/lib/stdlib/test/re_SUITE_data/testoutput2
+++ b/lib/stdlib/test/re_SUITE_data/testoutput2
@@ -5614,9 +5614,8 @@ No match
123456\P
No match
-//KF>testsavedregex
+//S-KF>testsavedregex
Compiled pattern written to testsavedregex
-Study data written to testsavedregex
/abc/IS>testsavedregex
Capturing subpattern count = 0
diff --git a/lib/stdlib/test/shell_docs_SUITE.erl b/lib/stdlib/test/shell_docs_SUITE.erl
index d8241f7530..11282db3e7 100644
--- a/lib/stdlib/test/shell_docs_SUITE.erl
+++ b/lib/stdlib/test/shell_docs_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2019. All Rights Reserved.
+%% Copyright Ericsson AB 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.
@@ -18,35 +18,39 @@
%% %CopyrightEnd%
%%
-module(shell_docs_SUITE).
--export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
- init_per_group/2,end_per_group/2]).
--export([init_per_testcase/2, end_per_testcase/2]).
+-export([all/0, suite/0, groups/0, init_per_suite/1, end_per_suite/1,
+ init_per_group/2, end_per_group/2]).
--export([render/1, links/1]).
+-export([render/1, links/1, normalize/1, render_prop/1]).
--include_lib("kernel/include/eep48.hrl").
-
-init_per_testcase(_Case, Config) ->
- Config.
+-export([render_all/1]).
-end_per_testcase(_Case, _Config) ->
- ok.
+-include_lib("kernel/include/eep48.hrl").
+-include_lib("stdlib/include/assert.hrl").
suite() ->
[{timetrap,{minutes,10}}].
all() ->
- [render, links].
+ [render, links, normalize, {group, prop}].
groups() ->
- [].
+ [{prop,[],[render_prop]}].
+%% Include a spec here in order to test that specs of undocumented functions
+%% is rendered correctly.
+-spec init_per_suite(Config1) -> Config2 when
+ Config1 :: list({atom(),term()}),
+ Config2 :: list({atom(),term()}).
init_per_suite(Config) ->
+ {ok, ?MODULE} = c:c(?MODULE,[debug_info]),
Config.
end_per_suite(_Config) ->
ok.
+init_per_group(prop, Config) ->
+ ct_property_test:init_per_suite(Config);
init_per_group(_GroupName, Config) ->
Config.
@@ -59,6 +63,7 @@ render(_Config) ->
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 ->
@@ -72,12 +77,25 @@ render(_Config) ->
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]
+ 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
- end).
+ end),
+ ok.
+
+render_prop(Config) ->
+% dbg:tracer(),dbg:p(all,c),dbg:tpl(shell_docs_prop,[]),
+ ct_property_test:quickcheck(
+ shell_docs_prop:prop_render(),Config).
links(_Config) ->
docsmap(
@@ -132,6 +150,17 @@ check_links(Mod, [C|T]) when is_binary(C) ->
check_links(_, []) ->
ok.
+normalize(_Config) ->
+ ?assertMatch(
+ [{p,[],[{em,[],[<<"a ">>,{code,[],[<<"b ">>]},<<"c">>]}]}],
+ shell_docs:normalize([{p,[],[{em,[],[<<" a ">>,{code,[],[<<" b ">>]},<<" c">>]}]}])
+ ),
+ ?assertMatch(
+ [{'div',[],[<<"!">>]}],
+ shell_docs:normalize([{'div',[],[{code,[],[<<" ">>,{i,[],[<<" ">>]}]},<<" !">>]}])
+ ),
+ ok.
+
%% Special binary_to_atom that deals with <<"'and'">>
b2a(Bin) ->
case erl_scan:string(binary_to_list(Bin)) of
@@ -139,21 +168,61 @@ b2a(Bin) ->
{ok,[{A,_}],_} -> A
end.
+%% Testing functions
+render_all(Dir) ->
+ file:make_dir(Dir),
+ docsmap(
+ fun(Mod, #docs_v1{ docs = Docs } = D) ->
+ SMod = atom_to_list(Mod),
+ file:write_file(filename:join(Dir,SMod ++ ".txt"),
+ unicode:characters_to_binary(shell_docs:render(Mod, D))),
+ file:write_file(filename:join(Dir,SMod ++ "_type.txt"),
+ unicode:characters_to_binary(shell_docs:render_type(Mod, D))),
+ file:write_file(filename:join(Dir,SMod ++ "_cb.txt"),
+ unicode:characters_to_binary(shell_docs:render_callback(Mod, D))),
+ lists:foreach(
+ fun({{function,Name,Arity},_Anno,_Sig,_Doc,_Meta}) ->
+ FName = SMod ++ "_"++atom_to_list(Name)++"_"++integer_to_list(Arity)++"_func.txt",
+ ok = file:write_file(filename:join(Dir,re:replace(FName,"[/:]","_",
+ [global,{return,list}])),
+ unicode:characters_to_binary(shell_docs:render(Mod, Name, Arity, D)));
+ ({{type,Name,Arity},_Anno,_Sig,_Doc,_Meta}) ->
+ FName = SMod ++ "_"++atom_to_list(Name)++"_"++integer_to_list(Arity)++"_type.txt",
+ ok = file:write_file(filename:join(Dir,re:replace(FName,"[/:]","_",
+ [global,{return,list}])),
+ unicode:characters_to_binary(shell_docs:render_type(Mod, Name, Arity, D)));
+ ({{callback,Name,Arity},_Anno,_Sig,_Doc,_Meta}) ->
+ FName = SMod ++ "_"++atom_to_list(Name)++"_"++integer_to_list(Arity)++"_cb.txt",
+ file:write_file(filename:join(Dir,re:replace(FName,"[/:]","_",
+ [global,{return,list}])),
+ unicode:characters_to_binary(shell_docs:render_callback(Mod, Name, Arity, D)))
+ end, Docs)
+ end).
+
docsmap(Fun) ->
- lists:map(fun F({Mod,_,_}) ->
- F(Mod);
- F(Mod) when is_list(Mod) ->
- F(list_to_atom(Mod));
- F(Mod) ->
- case code:get_doc(Mod) of
- {error, missing} ->
- ok;
- {error, cover_compiled} ->
- ok;
- {error, eacces} ->
- %% This can happen in BSD's for some reason...
- ok;
- {ok, Docs} ->
- Fun(Mod, Docs)
+ lists:map(
+ fun F({Mod,_,_}) ->
+ F(Mod);
+ F(Mod) when is_list(Mod) ->
+ F(list_to_atom(Mod));
+ F(Mod) ->
+ case code:get_doc(Mod) of
+ {error, missing} ->
+ ok;
+ {error, cover_compiled} ->
+ ok;
+ {error, E} when E =:= eperm; E =:= eacces ->
+ %% This can happen in BSD's for some reason...
+ ok;
+ {error, eisdir} ->
+ %% Uhm?
+ ok;
+ {ok, Docs} ->
+ try
+ Fun(Mod, Docs)
+ catch E:R:ST ->
+ io:format("Failed to render ~p~n~p:~p:~p~n",[Mod,E,R,ST]),
+ erlang:raise(E,R,ST)
end
- end, code:all_available()).
+ end
+ end, code:all_available()).
diff --git a/lib/stdlib/vsn.mk b/lib/stdlib/vsn.mk
index 02eee400bf..3aa3690d12 100644
--- a/lib/stdlib/vsn.mk
+++ b/lib/stdlib/vsn.mk
@@ -1 +1 @@
-STDLIB_VSN = 3.12.1
+STDLIB_VSN = 3.13
diff --git a/lib/syntax_tools/doc/src/notes.xml b/lib/syntax_tools/doc/src/notes.xml
index 9963ac41ae..52f085bf0c 100644
--- a/lib/syntax_tools/doc/src/notes.xml
+++ b/lib/syntax_tools/doc/src/notes.xml
@@ -32,6 +32,28 @@
<p>This document describes the changes made to the Syntax_Tools
application.</p>
+<section><title>Syntax_Tools 2.3</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p> Remove incomplete support for <c>cond</c>
+ expressions. </p>
+ <p>
+ Own Id: OTP-15925 Aux Id: PR-2304 </p>
+ </item>
+ <item>
+ <p>
+ Improved indentation for code generated with
+ <c>erl_prettypr</c> and <c>tidier</c>.</p>
+ <p>
+ Own Id: OTP-16386 Aux Id: PR-2451 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Syntax_Tools 2.2.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/syntax_tools/vsn.mk b/lib/syntax_tools/vsn.mk
index 9e6967d45d..87167529c3 100644
--- a/lib/syntax_tools/vsn.mk
+++ b/lib/syntax_tools/vsn.mk
@@ -1 +1 @@
-SYNTAX_TOOLS_VSN = 2.2.1
+SYNTAX_TOOLS_VSN = 2.3
diff --git a/lib/tools/doc/src/notes.xml b/lib/tools/doc/src/notes.xml
index 728343a86f..f4d2f0772f 100644
--- a/lib/tools/doc/src/notes.xml
+++ b/lib/tools/doc/src/notes.xml
@@ -31,6 +31,33 @@
</header>
<p>This document describes the changes made to the Tools application.</p>
+<section><title>Tools 3.4</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Updates for new <c>erlang:term_to_iovec()</c> BIF.</p>
+ <p>
+ Own Id: OTP-16128 Aux Id: OTP-15618 </p>
+ </item>
+ <item>
+ <p>Improved the presentation of allocations and carriers
+ in the <c>instrument</c> module.</p>
+ <p>
+ Own Id: OTP-16327</p>
+ </item>
+ <item>
+ <p>
+ Minor updates due to the new spawn improvements made.</p>
+ <p>
+ Own Id: OTP-16368 Aux Id: OTP-15251 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Tools 3.3.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/tools/src/tools.app.src b/lib/tools/src/tools.app.src
index beb5b98e15..f0c7ec1ead 100644
--- a/lib/tools/src/tools.app.src
+++ b/lib/tools/src/tools.app.src
@@ -43,6 +43,6 @@
]
},
{runtime_dependencies, ["stdlib-3.4","runtime_tools-1.8.14",
- "kernel-5.4","erts-9.1","compiler-5.0", "erts-@OTP-16327@"]}
+ "kernel-5.4","erts-9.1","compiler-5.0", "erts-11.0"]}
]
}.
diff --git a/lib/tools/test/fprof_SUITE.erl b/lib/tools/test/fprof_SUITE.erl
index 898d20f560..f5d68c20f0 100644
--- a/lib/tools/test/fprof_SUITE.erl
+++ b/lib/tools/test/fprof_SUITE.erl
@@ -501,7 +501,11 @@ cpu_create_file_slow(Config) when is_list(Config) ->
%%
{ok, [T, P]} = parse(AnalysisFile),
io:format("~p~n~n~p~n", [P, ets:tab2list(T)]),
- ok = verify(T, P),
+ try
+ ok = verify(T, P)
+ catch throw:{negative,_Weird} ->
+ io:format("Aborted as counter is negative because of bad cpu_time")
+ end,
Proc = pid_to_list(self()),
case P of
[{analysis_options, _},
@@ -520,13 +524,7 @@ cpu_create_file_slow(Config) when is_list(Config) ->
io:format("cpu_ts:~w, fprof:~w~n", [Acc, Acc1]),
{comment, io_lib:format("~p% cpu utilization", [100*divide(Acc,Acc1)])};
{'EXIT', not_supported} ->
- case {os:type(), os:version()} of
- {{unix, sunos}, {Major, Minor, _}}
- when Major >= 5, Minor >= 7 ->
- ct:fail(Result);
- _ ->
- {skipped, "not_supported"}
- end;
+ {skipped, "not_supported"};
_ ->
ct:fail(Result)
end,
@@ -633,6 +631,8 @@ verify(Tab, [{analysis_options, _},
{Proc, Cnt_P, Acc_P, Own_P} = Clocks
when Acc_P >= Own_P ->
Clocks;
+ {_, _, Acc_p, _} = Weird when Acc_p < 0 ->
+ throw({negative, Weird});
Weird ->
throw({error, [?MODULE, ?LINE, Weird]})
end
diff --git a/lib/tools/test/prof_bench_SUITE.erl b/lib/tools/test/prof_bench_SUITE.erl
index e8c4642d37..c0b9a7913c 100644
--- a/lib/tools/test/prof_bench_SUITE.erl
+++ b/lib/tools/test/prof_bench_SUITE.erl
@@ -32,7 +32,7 @@
suite() ->
- [{timetrap,{minutes,10}}].
+ [{timetrap,{minutes,15}}].
all() ->
[overhead].
diff --git a/lib/tools/vsn.mk b/lib/tools/vsn.mk
index 66f2c03149..b9f4811392 100644
--- a/lib/tools/vsn.mk
+++ b/lib/tools/vsn.mk
@@ -1 +1 @@
-TOOLS_VSN = 3.3.1
+TOOLS_VSN = 3.4
diff --git a/lib/wx/c_src/egl_impl.cpp b/lib/wx/c_src/egl_impl.cpp
index 61e05ee6f1..06c1de9cb6 100644
--- a/lib/wx/c_src/egl_impl.cpp
+++ b/lib/wx/c_src/egl_impl.cpp
@@ -48,6 +48,7 @@ int egl_initiated = 0;
#define OPENGLU_LIB L"glu32.dll"
typedef HMODULE DL_LIB_P;
typedef WCHAR DL_CHAR;
+#define DL_STR_FMT "%S"
void * dlsym(HMODULE Lib, const char *func) {
void * funcp;
if((funcp = (void *) GetProcAddress(Lib, func)))
@@ -67,6 +68,7 @@ void dlclose(HMODULE Lib) {
#else
typedef void * DL_LIB_P;
typedef char DL_CHAR;
+# define DL_STR_FMT "%s"
# ifdef _MACOSX
# define OPENGL_LIB "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib"
# define OPENGLU_LIB "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGLU.dylib"
@@ -125,7 +127,7 @@ int load_gl_functions() {
// dlclose(LIBhandle);
// fprintf(stderr, "OPENGL library is loaded\r\n");
} else {
- fprintf(stderr, "Could NOT load OpenGL library: %s\r\n", DLName);
+ fprintf(stderr, "Could NOT load OpenGL library: " DL_STR_FMT "\r\n", DLName);
};
DLName = (DL_CHAR *) OPENGLU_LIB;
@@ -154,7 +156,7 @@ int load_gl_functions() {
// dlclose(LIBhandle);
// fprintf(stderr, "GLU library is loaded\r\n");
} else {
- fprintf(stderr, "Could NOT load OpenGL GLU library: %s\r\n", DLName);
+ fprintf(stderr, "Could NOT load OpenGL GLU library: " DL_STR_FMT "\r\n", DLName);
};
return 1;
diff --git a/lib/wx/doc/src/notes.xml b/lib/wx/doc/src/notes.xml
index 7dcfbb1588..b9012054a8 100644
--- a/lib/wx/doc/src/notes.xml
+++ b/lib/wx/doc/src/notes.xml
@@ -32,6 +32,33 @@
<p>This document describes the changes made to the wxErlang
application.</p>
+<section><title>Wx 1.9.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix various compiler warnings on 64-bit Windows.</p>
+ <p>
+ Own Id: OTP-15800</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>Refactored the internal handling of deprecated and
+ removed functions.</p>
+ <p>
+ Own Id: OTP-16469</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Wx 1.9</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/wx/vsn.mk b/lib/wx/vsn.mk
index b498d21f3f..552e09ee2a 100644
--- a/lib/wx/vsn.mk
+++ b/lib/wx/vsn.mk
@@ -1 +1 @@
-WX_VSN = 1.9
+WX_VSN = 1.9.1
diff --git a/lib/xmerl/doc/src/notes.xml b/lib/xmerl/doc/src/notes.xml
index 997af9d037..d8b2852097 100644
--- a/lib/xmerl/doc/src/notes.xml
+++ b/lib/xmerl/doc/src/notes.xml
@@ -32,6 +32,22 @@
<p>This document describes the changes made to the Xmerl application.</p>
+<section><title>Xmerl 1.3.25</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p> Fix a bug that the function name didn't get
+ normalized in some case which left white spaces in links.
+ </p>
+ <p>
+ Own Id: OTP-16617</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Xmerl 1.3.24</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/xmerl/vsn.mk b/lib/xmerl/vsn.mk
index 79be4c8a95..8711ed946f 100644
--- a/lib/xmerl/vsn.mk
+++ b/lib/xmerl/vsn.mk
@@ -1 +1 @@
-XMERL_VSN = 1.3.24
+XMERL_VSN = 1.3.25